Skip to content

Commit 41fa3f4

Browse files
vudayaniBoykoAlex
authored andcommitted
Inject Bean completion proposal via Rewrite recipes
1 parent ca10970 commit 41fa3f4

File tree

20 files changed

+2382
-19
lines changed

20 files changed

+2382
-19
lines changed

headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/ICompletionProposal.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.util.Optional;
1515
import java.util.function.Supplier;
1616

17+
import org.eclipse.lsp4j.Command;
1718
import org.eclipse.lsp4j.CompletionItemKind;
1819
import org.springframework.ide.vscode.commons.util.Renderable;
1920

@@ -57,4 +58,9 @@ protected DocumentEdits transformEdit(DocumentEdits textEdit) {
5758
}
5859
};
5960
}
61+
62+
default Optional<Command> getCommand() {
63+
return Optional.empty();
64+
}
65+
6066
}

headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ private CompletionItem adaptItem(TextDocument doc, ICompletionProposal completio
300300
}
301301

302302
List<Object> commands = new ArrayList<>(2);
303+
completion.getCommand().ifPresent(commands::add);
303304
if (LspClient.currentClient() != LspClient.Client.ECLIPSE) {
304305
/*
305306
* Eclipse client always send completionItem resolve request before applying completion.
@@ -352,7 +353,9 @@ private void resolveCompletionItem(CompletionItem item, ICompletionProposal comp
352353
}
353354
}
354355
if (subCommands.size() == 1) {
355-
item.setCommand((Command)subCommands.get(0));
356+
Object o = subCommands.get(0);
357+
Command subCommand = o instanceof Command ? (Command) o : GSON.fromJson(o instanceof JsonElement ? (JsonElement) o : GSON.toJsonTree(o), Command.class);
358+
item.setCommand(subCommand);
356359
} else if (subCommands.isEmpty()) {
357360
item.setCommand(null);
358361
}

headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ protected CompletableFuture<Object> executeCommand(ExecuteCommandParams params)
315315
if (CODE_ACTION_COMMAND_ID.equals(params.getCommand())) {
316316
Assert.isLegal(params.getArguments().size()==2);
317317
QuickfixResolveParams quickfixParams = new QuickfixResolveParams(
318-
((JsonPrimitive)params.getArguments().get(0)).getAsString(), params.getArguments().get(1)
318+
params.getArguments().get(0) instanceof JsonPrimitive ? ((JsonPrimitive)params.getArguments().get(0)).getAsString() : params.getArguments().get(0).toString() , params.getArguments().get(1)
319319
);
320320
return quickfixResolve(quickfixParams)
321321
.flatMap((QuickfixEdit edit) -> {
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.spring;
17+
18+
import java.util.Objects;
19+
20+
import org.jspecify.annotations.NonNull;
21+
import org.jspecify.annotations.Nullable;
22+
import org.openrewrite.*;
23+
import org.openrewrite.properties.tree.Properties;
24+
import org.openrewrite.yaml.tree.Yaml;
25+
26+
public class CommentOutSpringPropertyKey extends Recipe {
27+
28+
@Override
29+
public String getDisplayName() {
30+
return "Comment out Spring properties";
31+
}
32+
33+
@Override
34+
public String getDescription() {
35+
return "Add comment to specified Spring properties, and comment out the property.";
36+
}
37+
38+
@Option(displayName = "Property key",
39+
description = "The name of the property key to comment out.",
40+
example = "management.metrics.binders.files.enabled")
41+
String propertyKey;
42+
43+
@Option(displayName = "Comment",
44+
description = "Comment to replace the property key.",
45+
example = "This property is deprecated and no longer applicable starting from Spring Boot 3.0.x")
46+
String comment;
47+
48+
49+
public CommentOutSpringPropertyKey(String propertyKey, String comment) {
50+
super();
51+
this.propertyKey = propertyKey;
52+
this.comment = comment;
53+
}
54+
55+
@Override
56+
public TreeVisitor<?, ExecutionContext> getVisitor() {
57+
Recipe changeProperties = new org.openrewrite.properties.AddPropertyComment(propertyKey, comment, true);
58+
Recipe changeYaml = new org.openrewrite.yaml.CommentOutProperty(propertyKey, comment, true);
59+
return new TreeVisitor<Tree, ExecutionContext>() {
60+
@Override
61+
public @Nullable Tree preVisit(@NonNull Tree tree, ExecutionContext ctx) {
62+
stopAfterPreVisit();
63+
if (tree instanceof Properties.File) {
64+
return changeProperties.getVisitor().visit(tree, ctx);
65+
} else if (tree instanceof Yaml.Documents) {
66+
return changeYaml.getVisitor().visit(tree, ctx);
67+
}
68+
return tree;
69+
}
70+
};
71+
}
72+
73+
public String getPropertyKey() {
74+
return propertyKey;
75+
}
76+
77+
public void setPropertyKey(String propertyKey) {
78+
this.propertyKey = propertyKey;
79+
}
80+
81+
public String getComment() {
82+
return comment;
83+
}
84+
85+
public void setComment(String comment) {
86+
this.comment = comment;
87+
}
88+
89+
@Override
90+
public int hashCode() {
91+
final int prime = 31;
92+
int result = super.hashCode();
93+
result = prime * result + Objects.hash(comment, propertyKey);
94+
return result;
95+
}
96+
97+
@Override
98+
public boolean equals(Object obj) {
99+
if (this == obj)
100+
return true;
101+
if (!super.equals(obj))
102+
return false;
103+
if (getClass() != obj.getClass())
104+
return false;
105+
CommentOutSpringPropertyKey other = (CommentOutSpringPropertyKey) obj;
106+
return Objects.equals(comment, other.comment) && Objects.equals(propertyKey, other.propertyKey);
107+
}
108+
109+
}

headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/ORDocUtils.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.eclipse.lsp4j.DeleteFile;
2222
import org.eclipse.lsp4j.Position;
2323
import org.eclipse.lsp4j.Range;
24+
import org.eclipse.lsp4j.ResourceOperation;
2425
import org.eclipse.lsp4j.TextDocumentEdit;
2526
import org.eclipse.lsp4j.TextEdit;
2627
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
@@ -71,6 +72,44 @@ public static Optional<DocumentEdits> computeEdits(IDocument doc, Result result)
7172
return Optional.empty();
7273

7374
}
75+
76+
public static Optional<DocumentEdits> computeDocumentEdits(WorkspaceEdit we, IDocument doc) {
77+
if (!we.getDocumentChanges().isEmpty()) {
78+
DocumentEdits edits = new DocumentEdits(doc, false);
79+
List<Either<TextDocumentEdit, ResourceOperation>> changes = we.getDocumentChanges();
80+
for (Either<TextDocumentEdit, ResourceOperation> change : changes) {
81+
if (change.isLeft()) {
82+
TextDocumentEdit textDocumentEdit = change.getLeft();
83+
List<TextEdit> textEdits = textDocumentEdit.getEdits();
84+
for (TextEdit textEdit : textEdits) {
85+
Range range = textEdit.getRange();
86+
Position start = range.getStart();
87+
Position end = range.getEnd();
88+
String newText = textEdit.getNewText();
89+
90+
try {
91+
int startOffset = doc.getLineOffset(start.getLine()) + start.getCharacter();
92+
int endOffset = doc.getLineOffset(end.getLine()) + end.getCharacter();
93+
94+
if (startOffset == endOffset) {
95+
edits.insert(startOffset, newText);
96+
} else if (newText.isEmpty()) {
97+
edits.delete(startOffset, endOffset);
98+
} else {
99+
edits.replace(startOffset, endOffset, newText);
100+
}
101+
} catch (BadLocationException ex) {
102+
log.error("Failed to apply text edit", ex);
103+
}
104+
}
105+
}
106+
}
107+
108+
return Optional.of(edits);
109+
}
110+
return Optional.empty();
111+
112+
}
74113

75114
public static Optional<TextDocumentEdit> computeTextDocEdit(TextDocument doc, String oldContent, String newContent, String changeAnnotationId) {
76115
TextDocument newDoc = new TextDocument(null, LanguageId.PLAINTEXT, 0, newContent);
@@ -172,7 +211,7 @@ public static void addToWorkspaceEdit(SimpleTextDocumentService documents, List<
172211
addToWorkspaceEdit(documents, docUri, oldContent, newContent, changeAnnotationId, we);
173212
}
174213
}
175-
214+
176215
public static void addToWorkspaceEdit(SimpleTextDocumentService documents, String docUri, String oldContent, String newContent, String changeAnnotationId, WorkspaceEdit we) {
177216
if(oldContent == null) {
178217
createNewFileEdit(docUri, newContent, changeAnnotationId, we);
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2017, 2024 Broadcom, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Broadcom, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.commons.rewrite.java;
12+
13+
import java.util.ArrayList;
14+
import java.util.Collections;
15+
import java.util.List;
16+
17+
import org.jspecify.annotations.NonNull;
18+
import org.openrewrite.ExecutionContext;
19+
import org.openrewrite.Recipe;
20+
import org.openrewrite.Tree;
21+
import org.openrewrite.TreeVisitor;
22+
import org.openrewrite.java.JavaIsoVisitor;
23+
import org.openrewrite.java.tree.J;
24+
import org.openrewrite.java.tree.JRightPadded;
25+
import org.openrewrite.java.tree.JavaType;
26+
import org.openrewrite.java.tree.Space;
27+
import org.openrewrite.java.tree.Statement;
28+
import org.openrewrite.java.tree.TypeTree;
29+
import org.openrewrite.java.tree.TypeUtils;
30+
import org.openrewrite.marker.Markers;
31+
32+
import com.fasterxml.jackson.annotation.JsonCreator;
33+
import com.fasterxml.jackson.annotation.JsonProperty;
34+
35+
/**
36+
* @author Udayani V
37+
*/
38+
public class AddFieldRecipe extends Recipe {
39+
40+
@Override
41+
public String getDisplayName() {
42+
return "Add field";
43+
}
44+
45+
@Override
46+
public String getDescription() {
47+
return "Add field desccription.";
48+
}
49+
50+
String fullyQualifiedName;
51+
52+
@NonNull
53+
String classFqName;
54+
55+
@JsonCreator
56+
public AddFieldRecipe(@NonNull @JsonProperty("fullyQualifiedClassName") String fullyQualifiedName, @NonNull @JsonProperty("classFqName") String classFqName) {
57+
this.fullyQualifiedName = fullyQualifiedName;
58+
this.classFqName = classFqName;
59+
}
60+
61+
@Override
62+
public TreeVisitor<?, ExecutionContext> getVisitor() {
63+
64+
return new JavaIsoVisitor<ExecutionContext>() {
65+
66+
JavaType.FullyQualified fullyQualifiedType = JavaType.ShallowClass.build(fullyQualifiedName);
67+
String fieldType = getFieldType(fullyQualifiedType);
68+
String fieldName = getFieldName(fullyQualifiedType);
69+
70+
@Override
71+
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
72+
if (TypeUtils.isOfClassType(classDecl.getType(), classFqName)) {
73+
74+
// Check if the class already has the field
75+
boolean hasOwnerRepoField = classDecl.getBody().getStatements().stream()
76+
.filter(J.VariableDeclarations.class::isInstance).map(J.VariableDeclarations.class::cast)
77+
.anyMatch(varDecl -> varDecl.getTypeExpression() != null
78+
&& varDecl.getTypeExpression().toString().equals(fieldType));
79+
80+
if (!hasOwnerRepoField) {
81+
J.VariableDeclarations newFieldDecl = new J.VariableDeclarations(
82+
Tree.randomId(),
83+
Space.build("\n\n", Collections.emptyList()),
84+
Markers.EMPTY,
85+
Collections.emptyList(),
86+
List.of(
87+
new J.Modifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, "private", J.Modifier.Type.Private, Collections.emptyList()),
88+
new J.Modifier(Tree.randomId(), Space.SINGLE_SPACE, Markers.EMPTY, "final", J.Modifier.Type.Final, Collections.emptyList())
89+
),
90+
TypeTree.build(fieldType),
91+
null,
92+
Collections.emptyList(),
93+
List.of(JRightPadded.build(new J.VariableDeclarations.NamedVariable(
94+
Tree.randomId(),
95+
Space.EMPTY,
96+
Markers.EMPTY,
97+
new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, Collections.emptyList(), fieldName, fullyQualifiedType, null),
98+
Collections.emptyList(),
99+
null,
100+
null
101+
)))
102+
);
103+
Statement formattedNewFieldDecl = autoFormat(classDecl.getBody().withStatements(List.of(newFieldDecl)), ctx, getCursor()).getStatements().get(0);
104+
List<Statement> newStatements = new ArrayList<>(classDecl.getBody().getStatements().size() + 1);
105+
newStatements.add(formattedNewFieldDecl);
106+
newStatements.addAll(classDecl.getBody().getStatements());
107+
classDecl = classDecl.withBody(classDecl.getBody().withStatements(newStatements));
108+
109+
110+
maybeAddImport(fullyQualifiedType.getFullyQualifiedName(), false);
111+
}
112+
return classDecl;
113+
}
114+
classDecl = (J.ClassDeclaration) super.visitClassDeclaration(classDecl, ctx);
115+
return classDecl;
116+
}
117+
};
118+
}
119+
120+
private static String getFieldName(JavaType.FullyQualified fullyQualifiedType) {
121+
return Character.toLowerCase(fullyQualifiedType.getClassName().charAt(0)) + fullyQualifiedType.getClassName().substring(1);
122+
}
123+
124+
private static String getFieldType(JavaType.FullyQualified fullyQualifiedType) {
125+
if(fullyQualifiedType.getOwningClass() != null) {
126+
String[] parts = fullyQualifiedType.getFullyQualifiedName().split("\\.");
127+
if (parts.length < 2) {
128+
return fullyQualifiedType.getClassName();
129+
}
130+
return parts[parts.length - 2] + "." + parts[parts.length - 1];
131+
}
132+
133+
return fullyQualifiedType.getClassName();
134+
}
135+
}

0 commit comments

Comments
 (0)