Skip to content

Commit 98c4c28

Browse files
committed
GH-1600: added web config indexing and showing summary code lens on controllers
1 parent 41ee0a7 commit 98c4c28

File tree

14 files changed

+520
-15
lines changed

14 files changed

+520
-15
lines changed

eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/Constants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ public class Constants {
5858

5959
public static final String PREF_CODELENS_QUERY_METHODS = "boot-java.java.codelens-over-query-methods";
6060

61+
public static final String PREF_CODELENS_WEB_CONFIGS_ON_CONTROLLER_CLASSES = "boot-java.java.codelens-web-configs-on-controller-classes";
62+
6163
public static final String PREF_AI_ENABLE_MCP = "boot-java.ai.mcp-server-enabled";
6264

6365
}

eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ private void sendConfiguration() {
226226
javaSettings.put("completions", javaCompletionSettings);
227227
javaSettings.put("reconcilers", preferenceStore.getBoolean(Constants.PREF_JAVA_RECONCILE));
228228
javaSettings.put("codelens-over-query-methods", preferenceStore.getBoolean(Constants.PREF_CODELENS_QUERY_METHODS));
229+
javaSettings.put("codelens-web-configs-on-controller-classes", preferenceStore.getBoolean(Constants.PREF_CODELENS_WEB_CONFIGS_ON_CONTROLLER_CLASSES));
229230

230231
bootJavaObj.put("jpql", preferenceStore.getBoolean(Constants.PREF_JPQL));
231232
bootJavaObj.put("live-information", liveInformation);

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaConfig.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,11 @@ public boolean isEnabledCodeLensOverDataQueryMethods() {
229229
return Boolean.TRUE.equals(b);
230230
}
231231

232+
public boolean isEnabledCodeLensForWebConfigs() {
233+
Boolean b = settings.getBoolean("boot-java", "java", "codelens-web-configs-on-controller-classes");
234+
return Boolean.TRUE.equals(b);
235+
}
236+
232237
public Settings getRawSettings() {
233238
return settings;
234239
}

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/Annotations.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ public class Annotations {
116116
public static final String BEAN_REGISTRAR_INTERFACE = "org.springframework.beans.factory.BeanRegistrar";
117117
public static final String BEAN_REGISTRY_INTERFACE = "org.springframework.beans.factory.BeanRegistry";
118118

119+
public static final String WEB_MVC_CONFIGURER_INTERFACE = "org.springframework.web.servlet.config.annotation.WebMvcConfigurer";
120+
public static final String WEB_MVC_API_VERSION_CONFIGURER_INTERFACE = "org.springframework.web.servlet.config.annotation.ApiVersionConfigurer";
119121

120122
public static final Map<String, String> AOP_ANNOTATIONS = Map.of(
121123
"org.aspectj.lang.annotation.Pointcut", "Pointcut",

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessLiveHoverUpdater;
6161
import org.springframework.ide.vscode.boot.java.requestmapping.LiveAppURLSymbolProvider;
6262
import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingHoverProvider;
63+
import org.springframework.ide.vscode.boot.java.requestmapping.WebConfigCodeLensProvider;
6364
import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxHandlerCodeLensProvider;
6465
import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxRouteHighlightProdivder;
6566
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
@@ -325,6 +326,7 @@ protected BootJavaCodeLensEngine createCodeLensEngine(SpringMetamodelIndex sprin
325326
codeLensProvider.add(new WebfluxHandlerCodeLensProvider(springIndex));
326327
codeLensProvider.add(new CopilotCodeLensProvider(projectFinder, server, spelSemanticTokens));
327328
codeLensProvider.add(new DataRepositoryAotMetadataCodeLensProvider(projectFinder, repositoryAotMetadataService, refactorings, config));
329+
codeLensProvider.add(new WebConfigCodeLensProvider(projectFinder, springIndex, config));
328330

329331
return new BootJavaCodeLensEngine(this, codeLensProvider);
330332
}

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.springframework.ide.vscode.boot.java.reconcilers.NotRegisteredBeansReconciler;
4747
import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteAstException;
4848
import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingIndexer;
49+
import org.springframework.ide.vscode.boot.java.requestmapping.WebConfigIndexer;
4950
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
5051
import org.springframework.ide.vscode.boot.java.utils.CachedSymbol;
5152
import org.springframework.ide.vscode.boot.java.utils.DefaultSymbolProvider;
@@ -148,6 +149,7 @@ private void createSymbol(TypeDeclaration type, Annotation node, ITypeBinding an
148149
indexRequestMappings(beanDefinition, type, annotationType, context, doc);
149150
indexConfigurationProperties(beanDefinition, type, context, doc);
150151
indexBeanRegistrarImplementation(beanDefinition, type, context, doc);
152+
indexWebConfig(beanDefinition, type, context, doc);
151153

152154
context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol));
153155
context.getBeans().add(new CachedBean(context.getDocURI(), beanDefinition));
@@ -456,6 +458,10 @@ private void indexBeanRegistrarImplementation(SpringIndexElement parentNode, Typ
456458
log.error("", e);
457459
}
458460
}
461+
462+
private void indexWebConfig(Bean beanDefinition, TypeDeclaration type, SpringIndexerJavaContext context, TextDocument doc) {
463+
WebConfigIndexer.indexWebConfig(beanDefinition, type, context, doc);
464+
}
459465

460466
private void scanBeanRegistryInvocations(SpringIndexElement parent, Block body, SpringIndexerJavaContext context, TextDocument doc) {
461467
if (body == null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Broadcom
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 - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.java.requestmapping;
12+
13+
import java.util.List;
14+
import java.util.Map;
15+
import java.util.Optional;
16+
17+
import org.eclipse.jdt.core.dom.ASTVisitor;
18+
import org.eclipse.jdt.core.dom.CompilationUnit;
19+
import org.eclipse.jdt.core.dom.ITypeBinding;
20+
import org.eclipse.jdt.core.dom.SimpleName;
21+
import org.eclipse.jdt.core.dom.TypeDeclaration;
22+
import org.eclipse.lsp4j.CodeLens;
23+
import org.eclipse.lsp4j.Command;
24+
import org.eclipse.lsp4j.Location;
25+
import org.eclipse.lsp4j.Range;
26+
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
27+
import org.springframework.ide.vscode.boot.app.BootJavaConfig;
28+
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
29+
import org.springframework.ide.vscode.boot.java.Annotations;
30+
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
31+
import org.springframework.ide.vscode.boot.java.handlers.CodeLensProvider;
32+
import org.springframework.ide.vscode.commons.java.IJavaProject;
33+
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
34+
import org.springframework.ide.vscode.commons.util.BadLocationException;
35+
import org.springframework.ide.vscode.commons.util.text.TextDocument;
36+
37+
public class WebConfigCodeLensProvider implements CodeLensProvider {
38+
39+
private final SpringMetamodelIndex springIndex;
40+
private final BootJavaConfig config;
41+
private final JavaProjectFinder projectFinder;
42+
43+
public WebConfigCodeLensProvider(JavaProjectFinder projectFinder, SpringMetamodelIndex springIndex, BootJavaConfig config) {
44+
this.projectFinder = projectFinder;
45+
this.springIndex = springIndex;
46+
this.config = config;
47+
}
48+
49+
@Override
50+
public void provideCodeLenses(CancelChecker cancelToken, TextDocument document, CompilationUnit cu, List<CodeLens> codeLenses) {
51+
if (!config.isEnabledCodeLensForWebConfigs()) {
52+
return;
53+
}
54+
55+
cu.accept(new ASTVisitor() {
56+
@Override
57+
public boolean visit(TypeDeclaration node) {
58+
provideCodeLens(cancelToken, node, document, codeLenses);
59+
return super.visit(node);
60+
}
61+
});
62+
63+
}
64+
65+
private void provideCodeLens(CancelChecker cancelToken, TypeDeclaration node, TextDocument doc, List<CodeLens> codeLenses) {
66+
cancelToken.checkCanceled();
67+
68+
ITypeBinding binding = node.resolveBinding();
69+
if (binding == null) return;
70+
71+
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(node);
72+
if (!annotationHierarchies.isAnnotatedWith(binding, Annotations.CONTROLLER)) return;
73+
74+
Optional<IJavaProject> optional = projectFinder.find(doc.getId());
75+
if (optional.isEmpty()) return;
76+
77+
IJavaProject project = optional.get();
78+
List<WebConfigIndexElement> webConfigs = springIndex.getNodesOfType(project.getElementName(), WebConfigIndexElement.class);
79+
80+
for (WebConfigIndexElement webConfig : webConfigs) {
81+
CodeLens codeLens = createCodeLens(webConfig, node, doc);
82+
if (codeLens != null) {
83+
codeLenses.add(codeLens);
84+
}
85+
}
86+
87+
}
88+
89+
private CodeLens createCodeLens(WebConfigIndexElement webConfig, TypeDeclaration node, TextDocument doc) {
90+
Command command = new Command();
91+
92+
// Display label
93+
String label = "Web Config";
94+
if (webConfig.isVersioningSupported()) {
95+
label += " - Versioning via " + webConfig.getVersionSupportStrategy();
96+
label += " - supported versions: " + String.join(", ", webConfig.getSupportedVersions());
97+
}
98+
99+
Location targetLocation = webConfig.getLocation();
100+
Range targetRange = targetLocation.getRange();
101+
102+
command.setTitle(label);
103+
command.setCommand("vscode.open");
104+
command.setArguments(List.of(targetLocation.getUri(),
105+
Map.of("selection", Map.of(
106+
"start", Map.of("line", targetRange.getStart().getLine(), "character", targetRange.getStart().getCharacter()),
107+
"end", Map.of("line", targetRange.getEnd().getLine(), "character", targetRange.getEnd().getCharacter()))
108+
)
109+
)
110+
);
111+
112+
// Range
113+
114+
SimpleName nameNode = node.getName();
115+
if (nameNode == null) return null;
116+
117+
Range range;
118+
try {
119+
range = doc.toRange(node.getStartPosition(), node.getLength());
120+
return new CodeLens(range, command, null);
121+
122+
} catch (BadLocationException e) {
123+
return null;
124+
}
125+
126+
}
127+
128+
129+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Broadcom
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 - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.java.requestmapping;
12+
13+
import java.util.ArrayList;
14+
import java.util.List;
15+
16+
import org.eclipse.lsp4j.Location;
17+
import org.springframework.ide.vscode.commons.protocol.spring.AbstractSpringIndexElement;
18+
19+
public class WebConfigIndexElement extends AbstractSpringIndexElement {
20+
21+
private final boolean isVersioningSupported;
22+
private final String versionSupportStrategy;
23+
private final String[] supportedVersions;
24+
private final Location location;
25+
26+
public WebConfigIndexElement(boolean isVersioningSupported, String versionSupportStrategy, String[] supportedVersions, Location location) {
27+
this.isVersioningSupported = isVersioningSupported;
28+
this.versionSupportStrategy = versionSupportStrategy;
29+
this.supportedVersions = supportedVersions;
30+
this.location = location;
31+
}
32+
33+
public boolean isVersioningSupported() {
34+
return isVersioningSupported;
35+
}
36+
37+
public String getVersionSupportStrategy() {
38+
return versionSupportStrategy;
39+
}
40+
41+
public String[] getSupportedVersions() {
42+
return supportedVersions;
43+
}
44+
45+
public Location getLocation() {
46+
return location;
47+
}
48+
49+
public static class Builder {
50+
51+
private boolean isVersioningSupported = false;
52+
private String versionSupportStrategy = null;
53+
private List<String> supportedVersions = new ArrayList<>(10);
54+
55+
public Builder isVersionSupported(boolean isVersionSupported) {
56+
this.isVersioningSupported = isVersionSupported;
57+
return this;
58+
}
59+
60+
public Builder versionStrategy(String versionSupportStrategy) {
61+
this.versionSupportStrategy = versionSupportStrategy;
62+
return this;
63+
}
64+
65+
public Builder supportedVersion(String supportedVersion) {
66+
this.supportedVersions.add(supportedVersion);
67+
return this;
68+
}
69+
70+
public WebConfigIndexElement buildFor(Location location) {
71+
return new WebConfigIndexElement(this.isVersioningSupported, this.versionSupportStrategy, (String[]) this.supportedVersions.toArray(new String[this.supportedVersions.size()]), location);
72+
}
73+
74+
}
75+
76+
}

0 commit comments

Comments
 (0)