Property place holder support inside SPEL
This commit is contained in:
@@ -19,7 +19,8 @@ import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemTy
|
||||
|
||||
public enum SpelProblemType implements ProblemType {
|
||||
|
||||
JAVA_SPEL_EXPRESSION_SYNTAX(ERROR, "SpEL parser raised a ParseException", "SpEL Expression Syntax");
|
||||
JAVA_SPEL_EXPRESSION_SYNTAX(ERROR, "SpEL parser raised a ParseException", "SpEL Expression Syntax"),
|
||||
PROPERTY_PLACE_HOLDER_SYNTAX(ERROR, "Property place holder raised a ParseException", "Property Place Holder Syntax");
|
||||
|
||||
private final ProblemSeverity defaultSeverity;
|
||||
private String description;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 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.data.jpa.queries;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
|
||||
public class AntlrUtils {
|
||||
|
||||
public static Stream<Token> getAllLeafs(ParserRuleContext ctx) {
|
||||
if (ctx.children == null) {
|
||||
return Stream.empty();
|
||||
}
|
||||
return ctx.children.stream().flatMap(n -> {
|
||||
if (n instanceof ParserRuleContext prc) {
|
||||
return getAllLeafs(prc);
|
||||
} else if (n instanceof TerminalNode tn) {
|
||||
return Stream.of(tn.getSymbol());
|
||||
}
|
||||
return Stream.empty();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,7 +26,6 @@ import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.antlr.v4.runtime.ConsoleErrorListener;
|
||||
import org.antlr.v4.runtime.Parser;
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.antlr.v4.runtime.RecognitionException;
|
||||
import org.antlr.v4.runtime.Recognizer;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
@@ -201,7 +200,7 @@ public class MySqlSemanticTokens implements SemanticTokensDataProvider {
|
||||
@Override
|
||||
public void exitUdfFunctionCall(UdfFunctionCallContext fc) {
|
||||
if (fc.fullId() != null) {
|
||||
List<Token> ls = getAllLeafs(fc.fullId()).toList();
|
||||
List<Token> ls = AntlrUtils.getAllLeafs(fc.fullId()).toList();
|
||||
if (!ls.isEmpty()) {
|
||||
semantics.put(ls.get(ls.size() - 1), "method");
|
||||
}
|
||||
@@ -211,13 +210,13 @@ public class MySqlSemanticTokens implements SemanticTokensDataProvider {
|
||||
@Override
|
||||
public void exitParameter(ParameterContext ctx) {
|
||||
if (ctx.dottedId() != null) {
|
||||
getAllLeafs(ctx.dottedId()).forEach(t -> semantics.put(t, "parameter"));
|
||||
AntlrUtils.getAllLeafs(ctx.dottedId()).forEach(t -> semantics.put(t, "parameter"));
|
||||
}
|
||||
if (ctx.uid() != null) {
|
||||
getAllLeafs(ctx.uid()).forEach(t -> semantics.put(t, "parameter"));
|
||||
AntlrUtils.getAllLeafs(ctx.uid()).forEach(t -> semantics.put(t, "parameter"));
|
||||
}
|
||||
if (ctx.decimalLiteral() != null) {
|
||||
getAllLeafs(ctx.decimalLiteral()).forEach(t -> semantics.put(t, "parameter"));
|
||||
AntlrUtils.getAllLeafs(ctx.decimalLiteral()).forEach(t -> semantics.put(t, "parameter"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,17 +277,6 @@ public class MySqlSemanticTokens implements SemanticTokensDataProvider {
|
||||
|
||||
}
|
||||
|
||||
static Stream<Token> getAllLeafs(ParserRuleContext ctx) {
|
||||
return ctx.children.stream().flatMap(n -> {
|
||||
if (n instanceof ParserRuleContext prc) {
|
||||
return getAllLeafs(prc);
|
||||
} else if (n instanceof TerminalNode tn) {
|
||||
return Stream.of(tn.getSymbol());
|
||||
}
|
||||
return Stream.empty();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ public class PostgreSqlSemanticTokens implements SemanticTokensDataProvider {
|
||||
@Override
|
||||
public void exitData_type(Data_typeContext dataType) {
|
||||
if (dataType.identifier() != null) {
|
||||
MySqlSemanticTokens.getAllLeafs(dataType.identifier())
|
||||
AntlrUtils.getAllLeafs(dataType.identifier())
|
||||
.filter(t -> t.getType() == PostgreSqlLexer.IDENTIFIER)
|
||||
.forEach(t -> semantics.put(t, "type"));
|
||||
}
|
||||
@@ -155,11 +155,11 @@ public class PostgreSqlSemanticTokens implements SemanticTokensDataProvider {
|
||||
@Override
|
||||
public void exitParameter(ParameterContext param) {
|
||||
if (param.identifier() != null) {
|
||||
MySqlSemanticTokens.getAllLeafs(param.identifier()).forEach(t -> semantics.put(t, "parameter"));
|
||||
AntlrUtils.getAllLeafs(param.identifier()).forEach(t -> semantics.put(t, "parameter"));
|
||||
} else if (param.INTEGER_LITERAL() != null) {
|
||||
semantics.put(param.INTEGER_LITERAL().getSymbol(), "parameter");
|
||||
} else if (param.reserved_keyword() != null) {
|
||||
MySqlSemanticTokens.getAllLeafs(param.reserved_keyword()).forEach(t -> semantics.put(t, "parameter"));
|
||||
AntlrUtils.getAllLeafs(param.reserved_keyword()).forEach(t -> semantics.put(t, "parameter"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 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.spel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.antlr.v4.runtime.ANTLRErrorListener;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.antlr.v4.runtime.ConsoleErrorListener;
|
||||
import org.antlr.v4.runtime.Parser;
|
||||
import org.antlr.v4.runtime.RecognitionException;
|
||||
import org.antlr.v4.runtime.Recognizer;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.atn.ATNConfigSet;
|
||||
import org.antlr.v4.runtime.dfa.DFA;
|
||||
import org.antlr.v4.runtime.tree.ErrorNode;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.AntlrUtils;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokenData;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokensDataProvider;
|
||||
import org.springframework.ide.vscode.parser.placeholder.PropertyPlaceHolderBaseListener;
|
||||
import org.springframework.ide.vscode.parser.placeholder.PropertyPlaceHolderLexer;
|
||||
import org.springframework.ide.vscode.parser.placeholder.PropertyPlaceHolderParser;
|
||||
import org.springframework.ide.vscode.parser.placeholder.PropertyPlaceHolderParser.DefaultValueContext;
|
||||
import org.springframework.ide.vscode.parser.placeholder.PropertyPlaceHolderParser.KeyContext;
|
||||
import org.springframework.ide.vscode.parser.placeholder.PropertyPlaceHolderParser.ValueContext;
|
||||
|
||||
public class PropertyPlaceHolderSemanticTokens implements SemanticTokensDataProvider {
|
||||
|
||||
private final Optional<Consumer<RecognitionException>> parseErrorHandler;
|
||||
|
||||
public PropertyPlaceHolderSemanticTokens(Optional<Consumer<RecognitionException>> parseErrorHandler) {
|
||||
this.parseErrorHandler = parseErrorHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTokenTypes() {
|
||||
return List.of("property", "string", "operator");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SemanticTokenData> computeTokens(String text, int initialOffset) {
|
||||
PropertyPlaceHolderLexer lexer = new PropertyPlaceHolderLexer(CharStreams.fromString(text));
|
||||
CommonTokenStream antlrTokens = new CommonTokenStream(lexer);
|
||||
PropertyPlaceHolderParser parser = new PropertyPlaceHolderParser(antlrTokens);
|
||||
|
||||
Map<Token, String> semantics = new HashMap<>();
|
||||
|
||||
lexer.removeErrorListener(ConsoleErrorListener.INSTANCE);
|
||||
parser.removeErrorListener(ConsoleErrorListener.INSTANCE);
|
||||
|
||||
List<SemanticTokenData> tokens = new ArrayList<>();
|
||||
|
||||
parser.addParseListener(new PropertyPlaceHolderBaseListener() {
|
||||
|
||||
@Override
|
||||
public void exitKey(KeyContext ctx) {
|
||||
// Remove all children nodes semantics as they will be replaced by a single token spanning multiple AST nodes
|
||||
AntlrUtils.getAllLeafs(ctx).forEach(semantics::remove);
|
||||
int start = ctx.getStart().getStartIndex() + initialOffset;
|
||||
int end = start + ctx.getText().length();
|
||||
tokens.add(new SemanticTokenData(start, end, "property", new String[0]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitDefaultValue(DefaultValueContext ctx) {
|
||||
AntlrUtils.getAllLeafs(ctx).forEach(semantics::remove);
|
||||
if (ctx.Colon() != null) {
|
||||
semantics.put(ctx.Colon().getSymbol(), "operator");
|
||||
}
|
||||
if (ctx.value() != null) {
|
||||
ValueContext valueCtx = ctx.value();
|
||||
int start = valueCtx.getStart().getStartIndex() + initialOffset;
|
||||
int end = start + valueCtx.getText().length();
|
||||
tokens.add(new SemanticTokenData(start, end, "string", new String[0]));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTerminal(TerminalNode node) {
|
||||
processTerminalNode(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitErrorNode(ErrorNode node) {
|
||||
processTerminalNode(node);
|
||||
}
|
||||
|
||||
private void processTerminalNode(TerminalNode node) {
|
||||
Token token = node.getSymbol();
|
||||
switch (token.getType()) {
|
||||
case PropertyPlaceHolderLexer.Space:
|
||||
case PropertyPlaceHolderLexer.LineBreak:
|
||||
case PropertyPlaceHolderLexer.EOF:
|
||||
break;
|
||||
case PropertyPlaceHolderLexer.Colon:
|
||||
semantics.put(node.getSymbol(), "operator");
|
||||
break;
|
||||
default:
|
||||
semantics.put(node.getSymbol(), "property");
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
parser.addErrorListener(new ANTLRErrorListener() {
|
||||
|
||||
@Override
|
||||
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine,
|
||||
String msg, RecognitionException e) {
|
||||
parseErrorHandler.ifPresent(h -> h.accept(e));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportContextSensitivity(Parser recognizer, DFA dfa, int startIndex, int stopIndex, int prediction,
|
||||
ATNConfigSet configs) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportAttemptingFullContext(Parser recognizer, DFA dfa, int startIndex, int stopIndex,
|
||||
BitSet conflictingAlts, ATNConfigSet configs) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportAmbiguity(Parser recognizer, DFA dfa, int startIndex, int stopIndex, boolean exact,
|
||||
BitSet ambigAlts, ATNConfigSet configs) {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
parser.start();
|
||||
|
||||
tokens.addAll(semantics.entrySet().stream()
|
||||
.map(e -> new SemanticTokenData(e.getKey().getStartIndex() + initialOffset,
|
||||
e.getKey().getStartIndex() + e.getKey().getText().length() + initialOffset, e.getValue(),
|
||||
new String[0]))
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
Collections.sort(tokens);
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,20 +10,29 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.spel;
|
||||
|
||||
import org.antlr.v4.runtime.Parser;
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.antlr.v4.runtime.tree.ErrorNode;
|
||||
import org.antlr.v4.runtime.tree.ParseTreeListener;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
import org.springframework.ide.vscode.boot.java.SpelProblemType;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.AntlrReconciler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.parser.placeholder.PropertyPlaceHolderLexer;
|
||||
import org.springframework.ide.vscode.parser.placeholder.PropertyPlaceHolderParser;
|
||||
import org.springframework.ide.vscode.parser.spel.SpelLexer;
|
||||
import org.springframework.ide.vscode.parser.spel.SpelParser;
|
||||
|
||||
public class SpelReconciler extends AntlrReconciler {
|
||||
|
||||
private boolean enabled;
|
||||
private AntlrReconciler propertyHolderReconciler;
|
||||
|
||||
public SpelReconciler() {
|
||||
super("SPEL", SpelParser.class, SpelLexer.class, "spelExpr", SpelProblemType.JAVA_SPEL_EXPRESSION_SYNTAX);
|
||||
this.errorOnUnrecognizedTokens = false;
|
||||
this.enabled = true;
|
||||
this.propertyHolderReconciler = new AntlrReconciler("Place-Holder", PropertyPlaceHolderParser.class, PropertyPlaceHolderLexer.class, "start", SpelProblemType.PROPERTY_PLACE_HOLDER_SYNTAX);
|
||||
}
|
||||
|
||||
public void setEnabled(boolean spelExpressionValidationEnabled) {
|
||||
@@ -38,4 +47,42 @@ public class SpelReconciler extends AntlrReconciler {
|
||||
super.reconcile(text, startPosition, problemCollector);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Parser createParser(String text, int startPosition, IProblemCollector problemCollector) throws Exception {
|
||||
Parser parser = super.createParser(text, startPosition, problemCollector);
|
||||
|
||||
// Reconcile embedded SPEL
|
||||
parser.addParseListener(new ParseTreeListener() {
|
||||
|
||||
private void processTerminal(TerminalNode node) {
|
||||
if (node.getSymbol().getType() == SpelLexer.PROPERTY_PLACE_HOLDER) {
|
||||
int placeHolderStartPosition = startPosition + node.getSymbol().getStartIndex() + 2;
|
||||
String content = node.getSymbol().getText().substring(2, node.getSymbol().getText().length() - 1);
|
||||
propertyHolderReconciler.reconcile(content, placeHolderStartPosition, problemCollector);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTerminal(TerminalNode node) {
|
||||
processTerminal(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitErrorNode(ErrorNode node) {
|
||||
processTerminal(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enterEveryRule(ParserRuleContext ctx) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitEveryRule(ParserRuleContext ctx) {
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return parser;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,8 +14,10 @@ import static org.springframework.ide.vscode.parser.spel.SpelLexer.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -50,9 +52,11 @@ import org.springframework.ide.vscode.parser.spel.SpelParserBaseListener;
|
||||
public class SpelSemanticTokens implements SemanticTokensDataProvider {
|
||||
|
||||
private final Optional<Consumer<RecognitionException>> parseErrorHandler;
|
||||
private final PropertyPlaceHolderSemanticTokens propertyPlaceHolderSemanticTokens;
|
||||
|
||||
public SpelSemanticTokens(Optional<Consumer<RecognitionException>> parseErrorHandler) {
|
||||
this.parseErrorHandler = parseErrorHandler;
|
||||
this.propertyPlaceHolderSemanticTokens = new PropertyPlaceHolderSemanticTokens(parseErrorHandler);
|
||||
}
|
||||
|
||||
public SpelSemanticTokens() {
|
||||
@@ -61,7 +65,9 @@ public class SpelSemanticTokens implements SemanticTokensDataProvider {
|
||||
|
||||
@Override
|
||||
public List<String> getTokenTypes() {
|
||||
return List.of("operator", "keyword", "type", "string", "number", "method", "property", "parameter");
|
||||
LinkedHashSet<String> tokenTypes = new LinkedHashSet<>(List.of("operator", "keyword", "type", "string", "number", "method", "property", "parameter"));
|
||||
tokenTypes.addAll(propertyPlaceHolderSemanticTokens.getTokenTypes());
|
||||
return tokenTypes.stream().toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -151,6 +157,9 @@ public class SpelSemanticTokens implements SemanticTokensDataProvider {
|
||||
case DOUBLE_QUOTED_STRING:
|
||||
semantics.put(node.getSymbol(), "string");
|
||||
break;
|
||||
case PROPERTY_PLACE_HOLDER:
|
||||
tokens.addAll(computeTokensFromPropertyPlaceHolderNode(node, initialOffset));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,5 +269,24 @@ public class SpelSemanticTokens implements SemanticTokensDataProvider {
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
private Collection<? extends SemanticTokenData> computeTokensFromPropertyPlaceHolderNode(TerminalNode node,
|
||||
int initialOffset) {
|
||||
List<SemanticTokenData> placeHolderTokens = new ArrayList<>();
|
||||
|
||||
int startPosition = initialOffset + node.getSymbol().getStartIndex();
|
||||
int placeHolderStartPosition = startPosition + 2;
|
||||
int endPosition = startPosition + node.getText().length();
|
||||
int placeHolderEndPosition = endPosition - 1;
|
||||
// '${' operator
|
||||
placeHolderTokens.add(new SemanticTokenData(startPosition, placeHolderStartPosition, "operator", new String[0]));
|
||||
// Property Place Holder contents
|
||||
placeHolderTokens.addAll(propertyPlaceHolderSemanticTokens.computeTokens(node.getText().substring(2, node.getText().length() - 1), placeHolderStartPosition));
|
||||
// '}' operator
|
||||
placeHolderTokens.add(new SemanticTokenData(placeHolderEndPosition, endPosition, "operator", new String[0]));
|
||||
return placeHolderTokens;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -334,6 +334,12 @@
|
||||
"label": "SpEL Expression Syntax",
|
||||
"description": "SpEL parser raised a ParseException",
|
||||
"defaultSeverity": "ERROR"
|
||||
},
|
||||
{
|
||||
"code": "PROPERTY_PLACE_HOLDER_SYNTAX",
|
||||
"label": "Property Place Holder Syntax",
|
||||
"description": "Property place holder raised a ParseException",
|
||||
"defaultSeverity": "ERROR"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.springframework.ide.vscode.boot.java.spel;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokenData;
|
||||
|
||||
public class PropertyPlaceHolderSemanticTokensTest {
|
||||
|
||||
private PropertyPlaceHolderSemanticTokens provider = new PropertyPlaceHolderSemanticTokens(Optional.of(Assertions::fail));
|
||||
|
||||
@Test
|
||||
void propertyWithDefault() {
|
||||
List<SemanticTokenData> tokens = provider.computeTokens("server.port:5673", 0);
|
||||
assertThat(tokens.size()).isEqualTo(3);
|
||||
assertThat(tokens.get(0)).isEqualTo(new SemanticTokenData(0, 11, "property", new String[0]));
|
||||
assertThat(tokens.get(1)).isEqualTo(new SemanticTokenData(11, 12, "operator", new String[0]));
|
||||
assertThat(tokens.get(2)).isEqualTo(new SemanticTokenData(12, 16, "string", new String[0]));
|
||||
}
|
||||
|
||||
@Test
|
||||
void propertyOnly() {
|
||||
List<SemanticTokenData> tokens = provider.computeTokens("server.port", 0);
|
||||
assertThat(tokens.size()).isEqualTo(1);
|
||||
assertThat(tokens.get(0)).isEqualTo(new SemanticTokenData(0, 11, "property", new String[0]));
|
||||
}
|
||||
|
||||
@Test
|
||||
void propertyWithEmptyDefaulValue() {
|
||||
List<SemanticTokenData> tokens = provider.computeTokens("server.port:", 0);
|
||||
assertThat(tokens.size()).isEqualTo(3);
|
||||
assertThat(tokens.get(0)).isEqualTo(new SemanticTokenData(0, 11, "property", new String[0]));
|
||||
assertThat(tokens.get(1)).isEqualTo(new SemanticTokenData(11, 12, "operator", new String[0]));
|
||||
assertThat(tokens.get(2)).isEqualTo(new SemanticTokenData(12, 12, "string", new String[0]));
|
||||
}
|
||||
|
||||
@Test
|
||||
void error_1() {
|
||||
provider = new PropertyPlaceHolderSemanticTokens(Optional.empty());
|
||||
List<SemanticTokenData> tokens = provider.computeTokens("server.:", 0);
|
||||
assertThat(tokens.size()).isEqualTo(3);
|
||||
assertThat(tokens.get(0)).isEqualTo(new SemanticTokenData(0, 6, "property", new String[0]));
|
||||
assertThat(tokens.get(1)).isEqualTo(new SemanticTokenData(6, 7, "property", new String[0]));
|
||||
assertThat(tokens.get(2)).isEqualTo(new SemanticTokenData(7, 8, "operator", new String[0]));
|
||||
}
|
||||
|
||||
@Test
|
||||
void error_2() {
|
||||
provider = new PropertyPlaceHolderSemanticTokens(Optional.empty());
|
||||
List<SemanticTokenData> tokens = provider.computeTokens("server.", 0);
|
||||
assertThat(tokens.size()).isEqualTo(2);
|
||||
assertThat(tokens.get(0)).isEqualTo(new SemanticTokenData(0, 6, "property", new String[0]));
|
||||
assertThat(tokens.get(1)).isEqualTo(new SemanticTokenData(6, 7, "property", new String[0]));
|
||||
}
|
||||
}
|
||||
@@ -143,5 +143,17 @@ public class SpelSemanticTokensTest {
|
||||
assertThat(tokens.get(8)).isEqualTo(new SemanticTokenData(21, 22, "operator", new String[0])); // )
|
||||
}
|
||||
|
||||
@Test
|
||||
void withPropertyPlaceHolder() {
|
||||
List<SemanticTokenData> tokens = provider.computeTokens("${server.port:8080} == 8080", 0);
|
||||
assertThat(tokens.size()).isEqualTo(7);
|
||||
assertThat(tokens.get(0)).isEqualTo(new SemanticTokenData(0, 2, "operator", new String[0])); // ${
|
||||
assertThat(tokens.get(1)).isEqualTo(new SemanticTokenData(2, 13, "property", new String[0])); // server.port
|
||||
assertThat(tokens.get(2)).isEqualTo(new SemanticTokenData(13, 14, "operator", new String[0])); // :
|
||||
assertThat(tokens.get(3)).isEqualTo(new SemanticTokenData(14, 18, "string", new String[0])); // 8080
|
||||
assertThat(tokens.get(4)).isEqualTo(new SemanticTokenData(18, 19, "operator", new String[0])); // }
|
||||
assertThat(tokens.get(5)).isEqualTo(new SemanticTokenData(20, 22, "operator", new String[0])); // ==
|
||||
assertThat(tokens.get(6)).isEqualTo(new SemanticTokenData(23, 27, "number", new String[0])); // 8080
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ package org.springframework.ide.vscode.boot.java.value.test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
@@ -43,6 +44,7 @@ import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
|
||||
import org.springframework.ide.vscode.boot.editor.harness.PropertyIndexHarness;
|
||||
import org.springframework.ide.vscode.boot.index.cache.IndexCache;
|
||||
import org.springframework.ide.vscode.boot.index.cache.IndexCacheVoid;
|
||||
import org.springframework.ide.vscode.boot.java.SpelProblemType;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaReconcileEngine;
|
||||
import org.springframework.ide.vscode.boot.java.links.SourceLinkFactory;
|
||||
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
|
||||
@@ -330,7 +332,7 @@ public class ValueSpelExpressionValidationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIgnoreSpelExpressionsWithPropertyPlaceholder() throws Exception {
|
||||
void testSpelExpressionsWithPropertyPlaceholder_noErrors() throws Exception {
|
||||
TextDocument doc = prepareDocument("@Value(\"onField\")", "@Value(value=\"#{${property.hello:false}}\")");
|
||||
assertNotNull(doc);
|
||||
|
||||
@@ -340,6 +342,23 @@ public class ValueSpelExpressionValidationTest {
|
||||
assertEquals(0, problems.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSpelExpressionsWithPropertyPlaceholder_withErrors() throws Exception {
|
||||
TextDocument doc = prepareDocument("@Value(\"onField\")", "@Value(value=\"#{${property.}}\")");
|
||||
assertNotNull(doc);
|
||||
|
||||
reconcileEngine.reconcile(doc, problemCollector);
|
||||
|
||||
List<ReconcileProblem> problems = problemCollector.getCollectedProblems();
|
||||
assertEquals(1, problems.size());
|
||||
|
||||
ReconcileProblem problem = problems.get(0);
|
||||
assertEquals(199, problem.getOffset());
|
||||
assertEquals(0, problem.getLength());
|
||||
assertEquals(SpelProblemType.PROPERTY_PLACE_HOLDER_SYNTAX, problem.getType());
|
||||
assertTrue(problem.getMessage().startsWith("Place-Holder:"));
|
||||
}
|
||||
|
||||
private TextDocument prepareDocument(String selectedAnnotation, String annotationStatementBeforeTest) throws Exception {
|
||||
String content = IOUtils.toString(new URI(docUri), StandardCharsets.UTF_8);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user