initial version of SpEL syntax validation

This commit is contained in:
Martin Lippert
2020-07-06 13:04:13 +02:00
parent f8369a64fa
commit 009ae98700
9 changed files with 161 additions and 4 deletions

View File

@@ -35,6 +35,8 @@ public class BootJavaConfig implements InitializingBean {
public static final int LIVE_INFORMATION_FETCH_DATA_RETRY_MAX_NO_DEFAULT = 10;
public static final int LIVE_INFORMATION_FETCH_DATA_RETRY_DELAY_IN_SECONDS_DEFAULT = 3;
public static final boolean VALIDAITON_SPEL_EXPRESSIONS_ENABLED_DEFAULT = true;
//TODO: Consider changing this to something that raises Spring application events.
@@ -110,6 +112,11 @@ public class BootJavaConfig implements InitializingBean {
return enabled != null && enabled.booleanValue();
}
public boolean isSpelExpressionValidationEnabled() {
Boolean enabled = settings.getBoolean("boot-java", "validation", "spel", "on");
return enabled != null ? enabled.booleanValue() : VALIDAITON_SPEL_EXPRESSIONS_ENABLED_DEFAULT;
}
public boolean areXmlHyperlinksEnabled() {
Boolean enabled = settings.getBoolean("boot-java", "support-spring-xml-config", "hyperlinks");
return enabled != null && enabled.booleanValue();

View File

@@ -15,6 +15,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.eclipse.lsp4j.CompletionItemKind;
@@ -32,6 +33,7 @@ import org.springframework.ide.vscode.boot.java.handlers.BootJavaCompletionEngin
import org.springframework.ide.vscode.boot.java.handlers.BootJavaDocumentHighlightEngine;
import org.springframework.ide.vscode.boot.java.handlers.BootJavaDocumentSymbolHandler;
import org.springframework.ide.vscode.boot.java.handlers.BootJavaHoverProvider;
import org.springframework.ide.vscode.boot.java.handlers.BootJavaReconcileEngine;
import org.springframework.ide.vscode.boot.java.handlers.BootJavaReferencesHandler;
import org.springframework.ide.vscode.boot.java.handlers.BootJavaWorkspaceSymbolHandler;
import org.springframework.ide.vscode.boot.java.handlers.CodeLensProvider;
@@ -70,6 +72,7 @@ import org.springframework.ide.vscode.commons.languageserver.completion.IComplet
import org.springframework.ide.vscode.commons.languageserver.composable.LanguageServerComponents;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
import org.springframework.ide.vscode.commons.languageserver.util.CodeLensHandler;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentHighlightHandler;
import org.springframework.ide.vscode.commons.languageserver.util.HoverHandler;
@@ -103,6 +106,8 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
private final SimpleLanguageServer server;
private final BootLanguageServerParams serverParams;
private final BootJavaConfig config;
private final SpringPropertyIndexProvider propertyIndexProvider;
private final ProjectBasedPropertyIndexProvider adHocPropertyIndexProvider;
@@ -120,7 +125,6 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
private SpringProcessTracker liveProcessTracker;
public BootJavaLanguageServerComponents(
SimpleLanguageServer server,
BootLanguageServerParams serverParams,
@@ -134,6 +138,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
) {
this.server = server;
this.serverParams = serverParams;
this.config = config;
projectFinder = serverParams.projectFinder;
projectObserver = serverParams.projectObserver;
@@ -197,7 +202,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
highlightsEngine = createDocumentHighlightEngine(indexer);
documents.onDocumentHighlight(highlightsEngine);
config.addListener(ignore -> {
log.info("update live process tracker settings - start");
@@ -245,6 +250,11 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
public DocumentHighlightHandler getDocumentHighlightHandler() {
return highlightsEngine;
}
@Override
public Optional<IReconcileEngine> getReconcileEngine() {
return Optional.of(new BootJavaReconcileEngine(this, config));
}
private void initialized() {
this.liveProcessTracker.start();

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016-2017 Pivotal, Inc.
* Copyright (c) 2016, 2020 Pivotal, 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
@@ -10,8 +10,30 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.handlers;
import java.net.URI;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.expression.ParseException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.ide.vscode.boot.app.BootJavaConfig;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
import org.springframework.ide.vscode.boot.java.value.Constants;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemTypes;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;
import org.springframework.ide.vscode.commons.util.text.IDocument;
/**
@@ -19,8 +41,104 @@ import org.springframework.ide.vscode.commons.util.text.IDocument;
*/
public class BootJavaReconcileEngine implements IReconcileEngine {
private static final Logger log = LoggerFactory.getLogger(BootJavaReconcileEngine.class);
private final JavaProjectFinder projectFinder;
private final BootJavaLanguageServerComponents server;
private final BootJavaConfig config;
public BootJavaReconcileEngine(BootJavaLanguageServerComponents server, BootJavaConfig config) {
this.server = server;
this.config = config;
this.projectFinder = this.server.getProjectFinder();
}
@Override
public void reconcile(IDocument doc, IProblemCollector problemCollector) {
public void reconcile(final IDocument doc, final IProblemCollector problemCollector) {
System.out.println("reconcile document: " + doc);
IJavaProject project = projectFinder.find(new TextDocumentIdentifier(doc.getUri())).orElse(null);
URI uri = URI.create(doc.getUri());
if (project != null) {
try {
problemCollector.beginCollecting();
server.getCompilationUnitCache().withCompilationUnit(project, uri, cu -> {
if (cu != null) {
reconcileAST(cu, problemCollector);
}
return null;
});
}
finally {
problemCollector.endCollecting();
}
}
}
private void reconcileAST(CompilationUnit cu, IProblemCollector problemCollector) {
cu.accept(new ASTVisitor() {
@Override
public boolean visit(SingleMemberAnnotation node) {
try {
visitAnnotation(node, problemCollector);
}
catch (Exception e) {
}
return super.visit(node);
}
});
}
protected void visitAnnotation(SingleMemberAnnotation node, IProblemCollector problemCollector) {
if (!config.isSpelExpressionValidationEnabled()) {
return;
}
ITypeBinding typeBinding = node.resolveTypeBinding();
if (typeBinding != null) {
String qname = typeBinding.getQualifiedName();
if (Constants.SPRING_VALUE.equals(qname)) {
Expression valueExp = node.getValue();
if (valueExp instanceof StringLiteral) {
String value = ((StringLiteral) valueExp).getLiteralValue();
if (value != null && value.startsWith("#{") && value.endsWith("}")) {
reconcileSpELExpressionValue(value, valueExp, problemCollector);
}
}
}
}
}
private void reconcileSpELExpressionValue(String value, Expression valueExp, IProblemCollector problemCollector) {
String spelExpression = value.substring(2, value.length() - 1);
if (spelExpression.length() > 0) {
SpelExpressionParser parser = new SpelExpressionParser();
try {
parser.parseExpression(spelExpression);
}
catch (ParseException e) {
String message = e.getSimpleMessage();
int position = e.getPosition();
createProblem(valueExp, message, position, problemCollector);
}
}
}
private void createProblem(Expression valueExp, String message, int position, IProblemCollector problemCollector) {
int start = valueExp.getStartPosition() + 3 + position;
int length = valueExp.getLength() - 5 - position;
ReconcileProblem problem = new ReconcileProblemImpl(ProblemTypes.create("SpEL Expression Problem", ProblemSeverity.ERROR), message, start, length);
problemCollector.accept(problem);
}
}