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

@@ -97,6 +97,7 @@ public class BootJavaPreferencesPage extends FieldEditorPreferencePage implement
});
addField(new BooleanFieldEditor(Constants.PREF_CHANGE_DETECTION, "Live Boot Change Detection", fieldEditorParent));
addField(new BooleanFieldEditor(Constants.PREF_VALIDATION_SPEL_EXPRESSIONS, "SpEL Expression Syntax Validation", fieldEditorParent));
}
}

View File

@@ -25,6 +25,8 @@ public class Constants {
public static final String PREF_SCAN_JAVA_TEST_SOURCES = "boot-java.scan-java-test-sources";
public static final String PREF_VALIDATION_SPEL_EXPRESSIONS = "boot-java.validation.spel.on";
public static final String PREF_SUPPORT_SPRING_XML_CONFIGS = "boot-java.support-spring-xml-config.on";
public static final String PREF_XML_CONFIGS_SCAN_FOLDERS = "boot-java.support-spring-xml-config.scan-folders-globs";
public static final String PREF_XML_CONFIGS_HYPERLINKS = "boot-java.support-spring-xml-config.hyperlinks";

View File

@@ -153,6 +153,8 @@ public class DelegatingStreamConnectionProvider implements StreamConnectionProvi
Map<String, Object> supportXML = new HashMap<>();
Map<String, Object> bootChangeDetection = new HashMap<>();
Map<String, Object> scanTestJavaSources = new HashMap<>();
Map<String, Object> validation = new HashMap<>();
Map<String, Object> validationSpelExpressions = new HashMap<>();
IPreferenceStore preferenceStore = BootLanguageServerPlugin.getDefault().getPreferenceStore();
@@ -172,10 +174,15 @@ public class DelegatingStreamConnectionProvider implements StreamConnectionProvi
bootChangeDetection.put("on", preferenceStore.getBoolean(Constants.PREF_CHANGE_DETECTION));
scanTestJavaSources.put("on", preferenceStore.getBoolean(Constants.PREF_SCAN_JAVA_TEST_SOURCES));
validationSpelExpressions.put("on", preferenceStore.getBoolean(Constants.PREF_VALIDATION_SPEL_EXPRESSIONS));
validation.put("spel", validationSpelExpressions);
bootJavaObj.put("live-information", liveInformation);
bootJavaObj.put("support-spring-xml-config", supportXML);
bootJavaObj.put("change-detection", bootChangeDetection);
bootJavaObj.put("scan-java-test-sources", scanTestJavaSources);
bootJavaObj.put("change-detection", bootChangeDetection);
bootJavaObj.put("validation", validation);
bootJavaObj.put("remote-apps", getAllRemoteApps());

View File

@@ -39,6 +39,7 @@ public class PrefsInitializer extends AbstractPreferenceInitializer {
preferenceStore.setDefault(Constants.PREF_XML_CONFIGS_SCAN_FOLDERS, "src/main");
preferenceStore.setDefault(Constants.PREF_CHANGE_DETECTION, false);
preferenceStore.setDefault(Constants.PREF_VALIDATION_SPEL_EXPRESSIONS, true);
preferenceStore.setDefault(Constants.PREF_SCAN_JAVA_TEST_SOURCES, false);
}

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);
}
}

View File

@@ -70,6 +70,11 @@ export const BootConfigSchema: PreferenceSchema = {
description: 'Enable/Disable detecting changes of running Spring Boot applications.',
default: false
},
'boot-java.validation.spel.on': {
type: 'boolean',
description: 'Validation - Validate SpEL Expression Syntax',
default: true
},
'boot-java.highlight-codelens.on': {
type: 'boolean',
default: true,
@@ -99,6 +104,7 @@ export interface BootConfiguration {
'boot-java.support-spring-xml-config.content-assist': boolean;
'boot-java.support-spring-xml-config.scan-folders': string;
'boot-java.change-detection.on': boolean;
'boot-java.validation.spel.on': boolean;
'boot-java.highlight-codelens.on': boolean;
'spring-boot.ls.javahome': string;
'spring-boot.ls.vmargs': string;

View File

@@ -129,6 +129,11 @@
"default": false,
"description": "Enable/Disable detecting changes of running Spring Boot applications"
},
"boot-java.validation.spel.on": {
"type": "boolean",
"default": false,
"description": "Validation - Validate SpEL Expression Syntax"
},
"boot-java.remote-apps": {
"type": "array",
"items": {