diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/embedded/lang/EmbeddedLangAstUtils.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/embedded/lang/EmbeddedLangAstUtils.java index d0aa2653f8..a87f92a774 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/embedded/lang/EmbeddedLangAstUtils.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/embedded/lang/EmbeddedLangAstUtils.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024 Broadcom, Inc. + * Copyright (c) 2024, 2025 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 @@ -14,7 +14,10 @@ import java.util.List; import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.FieldAccess; import org.eclipse.jdt.core.dom.InfixExpression; +import org.eclipse.jdt.core.dom.QualifiedName; +import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.StringLiteral; import org.eclipse.jdt.core.dom.TextBlock; @@ -50,6 +53,15 @@ public static EmbeddedLanguageSnippet extractEmbeddedExpression(Expression value } } return new CompositeEmbeddedLanguageSnippet(snippets); + } else if (valueExp instanceof SimpleName se) { + Object o = se.resolveConstantExpressionValue(); + if (o instanceof String v) { + return new StringConstantLanguageSnippet(valueExp.getStartPosition(), v); + } + } else if (valueExp instanceof FieldAccess fa) { + return extractEmbeddedExpression(fa.getName()); + } else if (valueExp instanceof QualifiedName qn) { + return extractEmbeddedExpression(qn.getName()); } return null; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/embedded/lang/StringConstantLanguageSnippet.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/embedded/lang/StringConstantLanguageSnippet.java new file mode 100644 index 0000000000..ef78d23005 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/embedded/lang/StringConstantLanguageSnippet.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2025 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.embedded.lang; + +import java.util.Collections; +import java.util.List; + +import org.springframework.ide.vscode.commons.util.text.IRegion; + +public class StringConstantLanguageSnippet implements EmbeddedLanguageSnippet { + + final private int offset; + final private String value; + + public StringConstantLanguageSnippet(int offset, String value) { + this.offset = offset; + this.value = value; + } + + @Override + public List toJavaRanges(IRegion range) { + return Collections.emptyList(); + } + + @Override + public int toJavaOffset(int offset) { + return this.offset; + } + + @Override + public String getText() { + return value; + } + +} diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/jpa/queries/JdtDataQuerySemanticTokensProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/jpa/queries/JdtDataQuerySemanticTokensProviderTest.java index 8bfaabbc66..25069ed9f0 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/jpa/queries/JdtDataQuerySemanticTokensProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/jpa/queries/JdtDataQuerySemanticTokensProviderTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024 Broadcom, Inc. + * Copyright (c) 2024, 2025 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 @@ -536,5 +536,121 @@ public interface OwnerRepository { } + @Test + void ConcatenatedStringWithConstantQuery() throws Exception { + String source = """ + package my.package + + import org.springframework.data.jpa.repository.Query; + + public interface OwnerRepository { + + static final String Q = " test FROM Te"; + + @Query(value = "SELECT" + + " DIS" + + "TINCT" + + Q + + "st", nativeQuery = true) + void findByLastName(); + } + """; + + String uri = Paths.get(jp.getLocationUri()).resolve("src/main/resource/my/package/OwnerRepository.java").toUri() + .toASCIIString(); + CompilationUnit cu = CompilationUnitCache.parse2(source.toCharArray(), uri, "OwnerRepository.java", jp); + assertThat(cu).isNotNull(); + + List tokens = computeTokens(cu); + + SemanticTokenData token = tokens.get(0); + assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("SELECT"); + assertThat(token).isEqualTo(new SemanticTokenData(171, 177, "keyword", new String[0])); + + token = tokens.get(1); + assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("DIS"); + assertThat(token).isEqualTo(new SemanticTokenData(185, 188, "keyword", new String[0])); + + token = tokens.get(2); + assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("TINCT"); + assertThat(token).isEqualTo(new SemanticTokenData(195, 200, "keyword", new String[0])); + +// token = tokens.get(3); +// assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("test"); +// assertThat(token).isEqualTo(new SemanticTokenData(165, 169, "variable", new String[0])); +// +// token = tokens.get(4); +// assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("FROM"); +// assertThat(token).isEqualTo(new SemanticTokenData(170, 174, "keyword", new String[0])); +// +// token = tokens.get(5); +// assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("Te"); +// assertThat(token).isEqualTo(new SemanticTokenData(175, 177, "variable", new String[0])); + + token = tokens.get(3); + assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("st"); + assertThat(token).isEqualTo(new SemanticTokenData(213, 215, "variable", new String[0])); + + } + + @Test + void ConcatenatedStringWithFieldAccessConstantQuery() throws Exception { + String source = """ + package my.package + + import org.springframework.data.jpa.repository.Query; + + public interface OwnerRepository { + + static class P { + static final String Q = " test FROM Te"; + } + + @Query(value = "SELECT" + + " DIS" + + "TINCT" + + P.Q + + "st", nativeQuery = true) + void findByLastName(); + } + """; + + String uri = Paths.get(jp.getLocationUri()).resolve("src/main/resource/my/package/OwnerRepository.java").toUri() + .toASCIIString(); + CompilationUnit cu = CompilationUnitCache.parse2(source.toCharArray(), uri, "OwnerRepository.java", jp); + + assertThat(cu).isNotNull(); + + List tokens = computeTokens(cu); + + SemanticTokenData token = tokens.get(0); + assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("SELECT"); + assertThat(token).isEqualTo(new SemanticTokenData(194, 200, "keyword", new String[0])); + + token = tokens.get(1); + assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("DIS"); + assertThat(token).isEqualTo(new SemanticTokenData(208, 211, "keyword", new String[0])); + + token = tokens.get(2); + assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("TINCT"); + assertThat(token).isEqualTo(new SemanticTokenData(218, 223, "keyword", new String[0])); + +// token = tokens.get(3); +// assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("test"); +// assertThat(token).isEqualTo(new SemanticTokenData(165, 169, "variable", new String[0])); +// +// token = tokens.get(4); +// assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("FROM"); +// assertThat(token).isEqualTo(new SemanticTokenData(170, 174, "keyword", new String[0])); +// +// token = tokens.get(5); +// assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("Te"); +// assertThat(token).isEqualTo(new SemanticTokenData(175, 177, "variable", new String[0])); + + token = tokens.get(3); + assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("st"); + assertThat(token).isEqualTo(new SemanticTokenData(238, 240, "variable", new String[0])); + + } }