PT #155474396: Ctrl-Click for Boot properties YAML

This commit is contained in:
BoykoAlex
2018-11-07 16:51:38 -05:00
parent 513c729e71
commit 4137819382
19 changed files with 880 additions and 489 deletions

View File

@@ -0,0 +1,21 @@
/*******************************************************************************
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.languageserver.util;
import java.util.Collection;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
public interface LanguageSpecific {
Collection<LanguageId> supportedLanguages();
}

View File

@@ -11,7 +11,9 @@
package org.springframework.ide.vscode.commons.yaml.completion;
import java.util.Collection;
import java.util.List;
import org.eclipse.lsp4j.Location;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
@@ -20,6 +22,8 @@ import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment;
import org.springframework.ide.vscode.commons.yaml.structure.YamlDocument;
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SNode;
import com.google.common.collect.ImmutableList;
/**
* @author Kris De Volder
*/
@@ -34,4 +38,12 @@ public interface YamlAssistContext extends YamlNavigable<YamlAssistContext> {
Renderable getValueHoverInfo(YamlDocument doc, DocumentRegion documentRegion);
YamlDocument getDocument();
default List<Location> getDefinitionsForPropertyKey() {
return ImmutableList.of();
}
default List<Location> getDefinitionsForPropertyValue(DocumentRegion valueRegion) {
return ImmutableList.of();
}
}

View File

@@ -10,6 +10,9 @@
*******************************************************************************/
package org.springframework.ide.vscode.languageserver.starter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.beans.factory.InitializingBean;
@@ -22,8 +25,15 @@ import org.springframework.ide.vscode.commons.languageserver.config.LanguageServ
import org.springframework.ide.vscode.commons.languageserver.config.LanguageServerProperties;
import org.springframework.ide.vscode.commons.languageserver.reconcile.DiagnosticSeverityProvider;
import org.springframework.ide.vscode.commons.languageserver.util.DefinitionHandler;
import org.springframework.ide.vscode.commons.languageserver.util.LanguageSpecific;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import org.springframework.util.Assert;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@Configuration
@EnableConfigurationProperties(LanguageServerProperties.class)
@@ -54,7 +64,31 @@ public class LanguageServerAutoConf {
@ConditionalOnBean(DefinitionHandler.class)
@Bean
InitializingBean registerDefintionHandler(SimpleTextDocumentService documents,
DefinitionHandler definitionHandler) {
return () -> documents.onDefinition(definitionHandler);
List<DefinitionHandler> definitionHandlers) {
if (definitionHandlers.size() == 1) {
return () -> documents.onDefinition(definitionHandlers.get(0));
} else {
Map<LanguageId, DefinitionHandler> handlers = new HashMap<>(definitionHandlers.size());
for (DefinitionHandler h : definitionHandlers) {
Assert.isInstanceOf(LanguageSpecific.class, h, "Only language specific defintion handlers supported!");
for (LanguageId l : ((LanguageSpecific)h).supportedLanguages()) {
Assert.isTrue(!handlers.containsKey(l), "Multiple definition handlers for the same language not supported!");
handlers.put(l, h);
}
}
ImmutableMap<LanguageId, DefinitionHandler> immutableMap = ImmutableMap.copyOf(handlers);
return () -> documents.onDefinition((position) -> {
TextDocument doc = documents.get(position.getTextDocument().getUri());
if (doc != null) {
LanguageId language = doc.getLanguageId();
DefinitionHandler handler = immutableMap.get(language);
if (handler != null) {
return handler.handle(position);
}
}
return ImmutableList.of();
});
}
}
}

View File

@@ -14,6 +14,8 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.context.annotation.Bean;
import org.springframework.ide.vscode.boot.common.PropertyCompletionFactory;
import org.springframework.ide.vscode.boot.common.RelaxedNameConfig;
import org.springframework.ide.vscode.boot.java.links.DefaultJavaElementLocationProvider;
import org.springframework.ide.vscode.boot.java.links.EclipseJavaDocumentUriProvider;
import org.springframework.ide.vscode.boot.java.links.EclipseJavaElementLocationProvider;
@@ -23,10 +25,21 @@ import org.springframework.ide.vscode.boot.java.links.JdtJavaDocumentUriProvider
import org.springframework.ide.vscode.boot.java.links.SourceLinkFactory;
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
import org.springframework.ide.vscode.boot.yaml.completions.ApplicationYamlAssistContext;
import org.springframework.ide.vscode.commons.languageserver.util.LspClient;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.util.FuzzyMap;
import org.springframework.ide.vscode.commons.util.LogRedirect;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider;
import org.springframework.ide.vscode.commons.yaml.ast.YamlParser;
import org.springframework.ide.vscode.commons.yaml.completion.YamlAssistContext;
import org.springframework.ide.vscode.commons.yaml.completion.YamlAssistContextProvider;
import org.springframework.ide.vscode.commons.yaml.structure.YamlDocument;
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureProvider;
import org.yaml.snakeyaml.Yaml;
@SpringBootApplication
public class BootLanguagServerBootApp {
@@ -75,4 +88,27 @@ public class BootLanguagServerBootApp {
}
}
@Bean Yaml yaml() {
return new Yaml();
}
@Bean YamlASTProvider yamlAstProvider(Yaml yaml) {
return new YamlParser(yaml);
}
@Bean YamlStructureProvider yamlStructureProvider() {
return YamlStructureProvider.DEFAULT;
}
@Bean YamlAssistContextProvider yamlAssistContextProvider(BootLanguageServerParams params, JavaElementLocationProvider javaElementLocationProvider) {
return new YamlAssistContextProvider() {
@Override
public YamlAssistContext getGlobalAssistContext(YamlDocument ydoc) {
IDocument doc = ydoc.getDocument();
FuzzyMap<PropertyInfo> index = params.indexProvider.getIndex(doc);
return ApplicationYamlAssistContext.global(ydoc, index, new PropertyCompletionFactory(), params.typeUtilProvider.getTypeUtil(doc), RelaxedNameConfig.COMPLETION_DEFAULTS, javaElementLocationProvider);
}
};
}
}

View File

@@ -15,6 +15,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
import org.springframework.ide.vscode.boot.java.links.JavaElementLocationProvider;
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
import org.springframework.ide.vscode.boot.properties.BootPropertiesLanguageServerComponents;
@@ -26,6 +27,9 @@ import org.springframework.ide.vscode.commons.languageserver.util.HoverHandler;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider;
import org.springframework.ide.vscode.commons.yaml.completion.YamlAssistContextProvider;
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureProvider;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@@ -36,6 +40,10 @@ public class BootLanguageServerInitializer implements InitializingBean {
@Autowired BootLanguageServerParams params;
@Autowired SourceLinks sourceLinks;
@Autowired CompilationUnitCache cuCache;
@Autowired JavaElementLocationProvider javaElementLocationProvider;
@Autowired YamlASTProvider parser;
@Autowired YamlStructureProvider yamlStructureProvider;
@Autowired YamlAssistContextProvider yamlAssistContextProvider;
private CompositeLanguageServerComponents components;
private VscodeCompletionEngineAdapter completionEngineAdapter;
@@ -58,7 +66,7 @@ public class BootLanguageServerInitializer implements InitializingBean {
//TODO: ComposableLanguageServer object instance serves no purpose anymore. The constructor really just contains
// some server intialization code. Migrate that code and get rid of the ComposableLanguageServer class
CompositeLanguageServerComponents.Builder builder = new CompositeLanguageServerComponents.Builder();
builder.add(new BootPropertiesLanguageServerComponents(server, (ignore) -> params));
builder.add(new BootPropertiesLanguageServerComponents(server, params, javaElementLocationProvider, parser, yamlStructureProvider, yamlAssistContextProvider));
builder.add(new BootJavaLanguageServerComponents(server, params, sourceLinks, cuCache));
components = builder.build(server);
params.projectObserver.addListener(reconcileOpenDocuments(server, components));

View File

@@ -40,7 +40,6 @@ import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFin
import org.springframework.ide.vscode.commons.languageserver.java.JavadocService;
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
import org.springframework.ide.vscode.commons.languageserver.jdt.ls.Classpath.CPE;
import org.springframework.ide.vscode.commons.languageserver.util.LSFactory;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.maven.MavenCore;
import org.springframework.ide.vscode.commons.maven.java.MavenProjectCache;

View File

@@ -10,6 +10,7 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.app;
import java.util.Collection;
import java.util.List;
import org.eclipse.lsp4j.Location;
@@ -18,23 +19,32 @@ import org.gradle.internal.impldep.com.google.common.collect.ImmutableList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ide.vscode.boot.java.links.JavaElementLocationProvider;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
import org.springframework.ide.vscode.boot.metadata.types.Type;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
import org.springframework.ide.vscode.boot.properties.hover.PropertiesDefinitionCalculator;
import org.springframework.ide.vscode.boot.properties.hover.PropertyFinder;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.IMember;
import org.springframework.ide.vscode.commons.languageserver.util.DefinitionHandler;
import org.springframework.ide.vscode.commons.languageserver.util.LanguageSpecific;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.FuzzyMap;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Key;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Node;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Value;
import org.springframework.stereotype.Component;
@Component
public class PropertiesJavaDefinitionHandler implements DefinitionHandler {
public class PropertiesJavaDefinitionHandler implements DefinitionHandler, LanguageSpecific {
@Autowired
private SimpleTextDocumentService documents;
@Autowired
private JavaElementLocationProvider javaDocumentLocationProvider;
private JavaElementLocationProvider javaElementLocationProvider;
@Autowired
private BootLanguageServerParams params;
@@ -47,10 +57,33 @@ public class PropertiesJavaDefinitionHandler implements DefinitionHandler {
FuzzyMap<PropertyInfo> index = params.indexProvider.getIndex(doc);
int offset;
offset = doc.toOffset(position.getPosition());
return new PropertiesDefinitionCalculator(javaDocumentLocationProvider, index, typeUtil, doc, offset).calculate();
return getDefinitions(index, typeUtil, doc, offset);
} catch (BadLocationException e) {
return ImmutableList.of();
}
}
private List<Location> getDefinitions(FuzzyMap<PropertyInfo> index, TypeUtil typeUtil, TextDocument doc, int offset) {
IJavaProject project = typeUtil.getJavaProject();
PropertyFinder propertyFinder = new PropertyFinder(index, typeUtil, doc, offset);
Node node = propertyFinder.findNode();
if (node instanceof Key) {
Collection<IMember> propertyJavaElements = PropertiesDefinitionCalculator.getPropertyJavaElements(propertyFinder, project, ((Key) node).decode());
return PropertiesDefinitionCalculator.getLocations(javaElementLocationProvider, project, propertyJavaElements);
} else if (node instanceof Value) {
Value value = (Value) node;
Key key = value.getParent().getKey();
Type type = PropertiesDefinitionCalculator.getPropertyType(propertyFinder, key.decode());
if (type != null) {
return PropertiesDefinitionCalculator.getValueDefinitionLocations(javaElementLocationProvider, typeUtil, type, value.decode());
}
}
return ImmutableList.of();
}
@Override
public Collection<LanguageId> supportedLanguages() {
return ImmutableList.of(LanguageId.BOOT_PROPERTIES);
}
}

View File

@@ -0,0 +1,131 @@
/*******************************************************************************
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.app;
import java.util.Collection;
import java.util.List;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.TextDocumentPositionParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ide.vscode.commons.languageserver.util.DefinitionHandler;
import org.springframework.ide.vscode.commons.languageserver.util.LanguageSpecific;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import org.springframework.ide.vscode.commons.yaml.ast.NodeRef;
import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider;
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
import org.springframework.ide.vscode.commons.yaml.completion.YamlAssistContext;
import org.springframework.ide.vscode.commons.yaml.completion.YamlAssistContextProvider;
import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment;
import org.springframework.ide.vscode.commons.yaml.structure.YamlDocument;
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureProvider;
import org.springframework.stereotype.Component;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeId;
import org.yaml.snakeyaml.parser.ParserException;
import org.yaml.snakeyaml.scanner.ScannerException;
import com.google.common.collect.ImmutableList;
@Component
public class YamlPropertiesJavaDefinitionHandler implements DefinitionHandler, LanguageSpecific {
private static final Logger log = LoggerFactory.getLogger(YamlPropertiesJavaDefinitionHandler.class);
@Autowired
private SimpleTextDocumentService documents;
@Autowired
private YamlASTProvider astProvider;
@Autowired
private YamlStructureProvider structureProvider;
@Autowired
private YamlAssistContextProvider assistContextProvider;
@Override
public Collection<LanguageId> supportedLanguages() {
return ImmutableList.of(LanguageId.BOOT_PROPERTIES_YAML);
}
@Override
public List<Location> handle(TextDocumentPositionParams position) {
try {
TextDocument doc = documents.get(position);
int offset = doc.toOffset(position.getPosition());
YamlFileAST ast = getAst(doc);
if (ast != null) {
YamlDocument ymlDoc = new YamlDocument(doc, structureProvider);
YamlAssistContext assistContext = assistContextProvider.getGlobalAssistContext(ymlDoc);
if (assistContext != null) {
List<NodeRef<?>> astPath = ast.findPath(offset);
final YamlPath path = YamlPath.fromASTPath(astPath);
if (path != null) {
YamlPath assistPath = path;
if (assistPath.pointsAtKey()) {
// When a path points at a key we must tramsform it to a
// 'value-terminating path'
// to be able to reuse the 'getHoverInfo' method on
// YamlAssistContext (as navigation
// into 'key' is not defined for YamlAssistContext.
String key = path.getLastSegment().toPropString();
assistPath = path.dropLast().append(YamlPathSegment.valueAt(key));
}
assistContext = assistPath.traverse(assistContext);
if (assistContext != null) {
if (path.pointsAtValue()) {
return assistContext.getDefinitionsForPropertyValue(getNodeRegion(ast, offset));
} else {
return assistContext.getDefinitionsForPropertyKey();
}
}
}
}
}
} catch (Exception e) {
log.error("", e);
}
return ImmutableList.of();
}
private DocumentRegion getNodeRegion(YamlFileAST ast, int offset) {
if (ast != null) {
Node n = ast.findNode(offset);
if (n != null && n.getNodeId() == NodeId.scalar) {
int start = n.getStartMark().getIndex();
int end = n.getEndMark().getIndex();
return new DocumentRegion(ast.getDocument(), start, end);
}
}
return null;
}
private YamlFileAST getAst(IDocument doc) throws Exception {
try {
return astProvider.getAST(doc);
} catch (ParserException | ScannerException e) {
// ignore, the user just typed some crap
}
return null;
}
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2014-2017 Pivotal, Inc.
* Copyright (c) 2014, 2018 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
@@ -19,7 +19,6 @@ import org.springframework.ide.vscode.boot.metadata.types.TypedProperty;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.util.FuzzyMap.Match;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.text.IDocument;
@@ -27,25 +26,25 @@ import org.springframework.ide.vscode.commons.yaml.hover.YPropertyInfoTemplates;
import org.springframework.ide.vscode.commons.yaml.schema.YType;
public class PropertyCompletionFactory {
public ICompletionProposal valueProposal(String value, String query, String niceTypeName, double score, DocumentEdits edits, Renderable info) {
return new ScoreableProposal() {
@Override
public DocumentEdits getTextEdit() {
return edits;
}
@Override
public String getLabel() {
return value;
}
@Override
public CompletionItemKind getKind() {
return CompletionItemKind.Value;
}
@Override
public double getBaseScore() {
return score;
@@ -105,7 +104,7 @@ public class PropertyCompletionFactory {
return getBaseDisplayString();
}
};
if (property.isDeprecated()) {
proposal.deprecate();
@@ -113,10 +112,7 @@ public class PropertyCompletionFactory {
return proposal;
}
private JavaProjectFinder documentContextFinder;
public PropertyCompletionFactory(JavaProjectFinder documentContextFinder) {
this.documentContextFinder = documentContextFinder;
public PropertyCompletionFactory() {
}
private class PropertyProposal extends AbstractPropertyProposal {
@@ -171,7 +167,7 @@ public class PropertyCompletionFactory {
public Renderable getDocumentation() {
return InformationTemplates.createCompletionDocumentation(match.data);
}
}
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016-2017 Pivotal, Inc.
* Copyright (c) 2016, 2018 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
@@ -546,6 +546,10 @@ public class TypeUtil {
}
}
public Type getMapKeyType(Type mapType) {
return isMap(mapType) ? getKeyType(mapType) : null;
}
public boolean isAssignableType(Type type) {
return ASSIGNABLE_TYPES.contains(type.getErasure())
|| isEnum(type)

View File

@@ -14,15 +14,12 @@ import java.util.Optional;
import java.util.Set;
import org.springframework.ide.vscode.boot.app.BootLanguageServerParams;
import org.springframework.ide.vscode.boot.common.PropertyCompletionFactory;
import org.springframework.ide.vscode.boot.common.RelaxedNameConfig;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
import org.springframework.ide.vscode.boot.java.links.JavaElementLocationProvider;
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtilProvider;
import org.springframework.ide.vscode.boot.properties.completions.SpringPropertiesCompletionEngine;
import org.springframework.ide.vscode.boot.properties.hover.PropertiesHoverInfoProvider;
import org.springframework.ide.vscode.boot.properties.reconcile.SpringPropertiesReconcileEngine;
import org.springframework.ide.vscode.boot.yaml.completions.ApplicationYamlAssistContext;
import org.springframework.ide.vscode.boot.yaml.reconcile.ApplicationYamlReconcileEngine;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine;
import org.springframework.ide.vscode.commons.languageserver.composable.LanguageServerComponents;
@@ -32,22 +29,16 @@ import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFin
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.HoverHandler;
import org.springframework.ide.vscode.commons.languageserver.util.LSFactory;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.util.FuzzyMap;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider;
import org.springframework.ide.vscode.commons.yaml.ast.YamlParser;
import org.springframework.ide.vscode.commons.yaml.completion.YamlAssistContext;
import org.springframework.ide.vscode.commons.yaml.completion.YamlAssistContextProvider;
import org.springframework.ide.vscode.commons.yaml.completion.YamlCompletionEngine;
import org.springframework.ide.vscode.commons.yaml.completion.YamlCompletionEngineOptions;
import org.springframework.ide.vscode.commons.yaml.hover.YamlHoverInfoProvider;
import org.springframework.ide.vscode.commons.yaml.structure.YamlDocument;
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureProvider;
import org.yaml.snakeyaml.Yaml;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -62,10 +53,10 @@ public class BootPropertiesLanguageServerComponents implements LanguageServerCom
private static final String YML = ".yml";
private static final String PROPERTIES = ".properties";
private static final Set<LanguageId> LANGUAGES = ImmutableSet.of(
LanguageId.BOOT_PROPERTIES,
LanguageId.BOOT_PROPERTIES_YAML
LanguageId.BOOT_PROPERTIES,
LanguageId.BOOT_PROPERTIES_YAML
);
private static final YamlCompletionEngineOptions COMPLETION_OPTIONS = new YamlCompletionEngineOptions() {
@@ -77,35 +68,28 @@ public class BootPropertiesLanguageServerComponents implements LanguageServerCom
private final JavaProjectFinder javaProjectFinder;
private final SpringPropertyIndexProvider indexProvider;
private final TypeUtilProvider typeUtilProvider;
private final RelaxedNameConfig relaxedNameConfig = RelaxedNameConfig.COMPLETION_DEFAULTS;
private final PropertyCompletionFactory completionFactory;
// For yaml
private final Yaml yaml = new Yaml();
private final YamlASTProvider parser = new YamlParser(yaml);
private final YamlStructureProvider yamlStructureProvider= YamlStructureProvider.DEFAULT;
private final YamlStructureProvider yamlStructureProvider;
private YamlAssistContextProvider yamlAssistContextProvider;
private final SimpleLanguageServer server;
private YamlASTProvider parser;
public BootPropertiesLanguageServerComponents(SimpleLanguageServer server, LSFactory<BootLanguageServerParams> _params) {
public BootPropertiesLanguageServerComponents(
SimpleLanguageServer server,
BootLanguageServerParams serverParams,
JavaElementLocationProvider javaElementLocationProvider,
YamlASTProvider parser,
YamlStructureProvider yamlStructureProvider,
YamlAssistContextProvider yamlAssistContextProvider) {
this.server = server;
BootLanguageServerParams serverParams = _params.create(server);
this.parser = parser;
this.indexProvider = serverParams.indexProvider;
this.typeUtilProvider = serverParams.typeUtilProvider;
this.javaProjectFinder = serverParams.projectFinder;
this.projectObserver = serverParams.projectObserver;
this.completionFactory = new PropertyCompletionFactory(javaProjectFinder);
this.yamlAssistContextProvider = new YamlAssistContextProvider() {
@Override
public YamlAssistContext getGlobalAssistContext(YamlDocument ydoc) {
IDocument doc = ydoc.getDocument();
FuzzyMap<PropertyInfo> index = indexProvider.getIndex(doc);
return ApplicationYamlAssistContext.global(ydoc, index, completionFactory, typeUtilProvider.getTypeUtil(doc), relaxedNameConfig);
}
};
this.yamlStructureProvider = yamlStructureProvider;
this.yamlAssistContextProvider = yamlAssistContextProvider;
}

View File

@@ -40,7 +40,7 @@ public class SpringPropertiesCompletionEngine implements ICompletionEngine {
public SpringPropertiesCompletionEngine(SpringPropertyIndexProvider indexProvider, TypeUtilProvider typeUtilProvider, JavaProjectFinder projectFinder) {
this.indexProvider = indexProvider;
this.typeUtilProvider = typeUtilProvider;
this.completionFactory = new PropertyCompletionFactory(projectFinder);
this.completionFactory = new PropertyCompletionFactory();
}
/**

View File

@@ -22,17 +22,15 @@ import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.links.JavaElementLocationProvider;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo.PropertySource;
import org.springframework.ide.vscode.boot.metadata.types.Type;
import org.springframework.ide.vscode.boot.metadata.types.TypeParser;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
import org.springframework.ide.vscode.commons.java.IField;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.IMember;
import org.springframework.ide.vscode.commons.java.IMethod;
import org.springframework.ide.vscode.commons.java.IType;
import org.springframework.ide.vscode.commons.util.FuzzyMap;
import org.springframework.ide.vscode.commons.util.StringUtil;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Key;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Node;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Value;
import com.google.common.collect.ImmutableList;
@@ -40,117 +38,57 @@ public class PropertiesDefinitionCalculator {
private static final Logger log = LoggerFactory.getLogger(PropertiesDefinitionCalculator.class);
final private PropertyFinder propertyFinder;
final private IJavaProject project;
final private JavaElementLocationProvider javaElementLocationProvider;
public PropertiesDefinitionCalculator(JavaElementLocationProvider javaElementLocationProvider, FuzzyMap<PropertyInfo> index, TypeUtil typeUtil, IDocument doc, int offset) {
this.javaElementLocationProvider = javaElementLocationProvider;
this.propertyFinder = new PropertyFinder(index, typeUtil, doc, offset);
this.project = typeUtil.getJavaProject();
public static List<Location> getLocations(JavaElementLocationProvider locationProvider, IJavaProject project,
Collection<IMember> propertyJavaElements) {
return propertyJavaElements.stream().map(member -> locationProvider.findLocation(project, member))
.filter(Objects::nonNull).collect(Collectors.toList());
}
public List<Location> calculate() {
Node node = propertyFinder.findNode();
if (node instanceof Key) {
Collection<IMember> propertyJavaElements = getPropertyJavaElements(((Key) node).decode());
return getPropertyDefintion(propertyJavaElements);
} else if (node instanceof Value) {
Value value = (Value) node;
Key key = value.getParent().getKey();
String javaTypeFqName = getPropertyJavaTypeFqName(key.decode());
if (javaTypeFqName != null) {
// Class reference value link
if ("java.lang.Class".equals(javaTypeFqName)) {
IType javaType = project.findType(value.decode());
if (javaType != null) {
Location location = findLocation(javaType);
if (location != null) {
return ImmutableList.of(location);
}
}
}
IType javaType = project.findType(javaTypeFqName);
if (javaType != null) {
// Enum value link
if (javaType.isEnum()) {
String enumValue = StringUtil.hyphensToUpperCase(StringUtil.camelCaseToHyphens(value.decode()));
ImmutableList.Builder<Location> list = ImmutableList.builder();
javaType.getFields().forEach(field -> {
if (field.getElementName().equals(enumValue)) {
Location location = findLocation(field);
if (location != null) {
list.add(location);
}
}
});
return list.build();
}
}
}
}
return ImmutableList.of();
}
private List<Location> getPropertyDefintion(Collection<IMember> propertyJavaElements) {
return propertyJavaElements.stream().map(this::findLocation).filter(Objects::nonNull).collect(Collectors.toList());
}
private Location findLocation(IMember element) {
return javaElementLocationProvider.findLocation(project, element);
}
private String getPropertyJavaTypeFqName(String propertyKey) {
public static Type getPropertyType(PropertyFinder propertyFinder, String propertyKey) {
PropertyInfo best = propertyFinder.findBestHoverMatch(propertyKey);
if (best != null) {
String type = best.getType();
// Trim down generic type if present
int idx = type == null ? -1 : type.indexOf('<');
return idx < 0 ? type : type.substring(0, idx);
return TypeParser.parse(best.getType());
}
return null;
}
private Collection<IMember> getPropertyJavaElements(String propertyKey) {
public static Collection<IMember> getPropertyJavaElements(PropertyFinder propertyFinder, IJavaProject project, String propertyKey) {
PropertyInfo best = propertyFinder.findBestHoverMatch(propertyKey);
if (best != null) {
List<PropertySource> sources = best.getSources();
if (sources != null) {
ImmutableList.Builder<IMember> elements = ImmutableList.builder();
for (PropertySource source : sources) {
String typeName = source.getSourceType();
if (typeName!=null) {
IType type = project.findType(typeName);
IMethod method = null;
if (type!=null) {
String methodSig = source.getSourceMethod();
if (methodSig!=null) {
method = getMethod(type, methodSig);
} else {
method = getSetter(type, best);
}
}
if (method!=null) {
elements.add(method);
} else if (type!=null) {
elements.add(type);
}
}
}
return elements.build();
}
return getPropertyJavaElement(project, best);
}
return ImmutableList.of();
}
private IMethod getMethod(IType type, String methodSig) {
public static Collection<IMember> getPropertyJavaElement(IJavaProject project, PropertyInfo property) {
List<PropertySource> sources = property.getSources();
ImmutableList.Builder<IMember> elements = ImmutableList.builder();
if (sources != null) {
for (PropertySource source : sources) {
String typeName = source.getSourceType();
if (typeName!=null) {
IType type = project.findType(typeName);
IMethod method = null;
if (type!=null) {
String methodSig = source.getSourceMethod();
if (methodSig!=null) {
method = getMethod(type, methodSig);
} else {
method = getPropertyMethod(type, property.getName());
}
}
if (method!=null) {
elements.add(method);
} else if (type!=null) {
elements.add(type);
}
}
}
}
return elements.build();
}
private static IMethod getMethod(IType type, String methodSig) {
String name = getMethodName(methodSig);
//TODO: This code assumes 0 arguments, which is the case currently for all
// 'real' data in spring jars.
@@ -184,10 +122,9 @@ public class PropertiesDefinitionCalculator {
* Attempt to find corresponding setter method for a given property.
* @return setter method, or null if not found.
*/
private IMethod getSetter(IType type, PropertyInfo propertyInfo) {
private static IMethod getAccessor(IType type, String getOrSet, String propName) {
try {
String propName = propertyInfo.getName();
String setterName = "set"
String setterName = getOrSet
+Character.toUpperCase(propName.charAt(0))
+toCamelCase(propName.substring(1));
String sloppySetterName = setterName.toLowerCase();
@@ -209,11 +146,22 @@ public class PropertiesDefinitionCalculator {
}
}
public static IMethod getPropertyMethod(IType type, String propName) {
String[] accessors = { "set", "get", "is" };
for (String a : accessors) {
IMethod propertyMethod = getAccessor(type, a, propName);
if (propertyMethod != null) {
return propertyMethod;
}
}
return null;
}
/**
* Convert hyphened name to camel case name. It is
* safe to call this on an already camel-cased name.
*/
private String toCamelCase(String name) {
private static String toCamelCase(String name) {
if (name.isEmpty()) {
return name;
} else {
@@ -234,4 +182,58 @@ public class PropertiesDefinitionCalculator {
}
}
public static IField getEnumField(IType type, String value) {
String[] enumValues = {
value,
StringUtil.hyphensToUpperCase(value),
StringUtil.hyphensToUpperCase(StringUtil.camelCaseToHyphens(value))
};
for (String enumValue : enumValues) {
IField field = type.getField(enumValue);
if (field != null) {
return field;
}
}
return null;
}
private static List<Location> getEnumValueDefinitionLocation(JavaElementLocationProvider javaElementLocationProvider, IJavaProject project, Type type, String value) {
IType javaType = project.findType(type.getErasure());
if (javaType != null) {
IField field = getEnumField(javaType, value);
if (field != null) {
Location location = javaElementLocationProvider.findLocation(project, field);
if (location != null) {
return ImmutableList.of(location);
}
}
}
return ImmutableList.of();
}
private static List<Location> getClassValueDefinitionLocation(JavaElementLocationProvider javaElementLocationProvider, IJavaProject project, String value) {
IType javaType = project.findType(value);
if (javaType != null) {
Location location = javaElementLocationProvider.findLocation(project, javaType);
if (location != null) {
return ImmutableList.of(location);
}
}
return ImmutableList.of();
}
public static List<Location> getValueDefinitionLocations(JavaElementLocationProvider javaElementLocationProvider, TypeUtil typeUtil, Type type, String value) {
IJavaProject project = typeUtil.getJavaProject();
if (TypeUtil.isClass(type)) {
return getClassValueDefinitionLocation(javaElementLocationProvider, project, value);
}
if (typeUtil.isEnum(type)) {
return getEnumValueDefinitionLocation(javaElementLocationProvider, project, type, value);
}
return ImmutableList.of();
}
}

View File

@@ -21,7 +21,7 @@ import org.springframework.ide.vscode.java.properties.antlr.parser.AntlrParser;
import org.springframework.ide.vscode.java.properties.parser.ParseResults;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Node;
class PropertyFinder {
public class PropertyFinder {
final FuzzyMap<PropertyInfo> index;
final TypeUtil typeUtil;
@@ -29,7 +29,7 @@ class PropertyFinder {
final int offset;
final AntlrParser parser;
PropertyFinder(FuzzyMap<PropertyInfo> index, TypeUtil typeUtil, IDocument doc, int offset) {
public PropertyFinder(FuzzyMap<PropertyInfo> index, TypeUtil typeUtil, IDocument doc, int offset) {
this.index = index;
this.typeUtil = typeUtil;
this.doc = doc;
@@ -37,12 +37,12 @@ class PropertyFinder {
this.parser = new AntlrParser();
}
Node findNode() {
public Node findNode() {
ParseResults parseResults = parser.parse(doc.get());
return parseResults.ast.findNode(offset);
}
DocumentRegion createRegion(Node value) {
public DocumentRegion createRegion(Node value) {
// Trim trailing spaces (there is no leading white space already)
int length = value.getLength();
try {
@@ -56,7 +56,7 @@ class PropertyFinder {
/**
* Search known properties for the best 'match' to show as hover data.
*/
PropertyInfo findBestHoverMatch(String propName) {
public PropertyInfo findBestHoverMatch(String propName) {
PropertyInfo propertyInfo = index.get(propName);
if (propertyInfo == null) {
propertyInfo = SpringPropertyIndex.findLongestValidProperty(index, propName);

View File

@@ -20,6 +20,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.lsp4j.Location;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.common.InformationTemplates;
@@ -27,6 +28,7 @@ import org.springframework.ide.vscode.boot.common.PropertyCompletionFactory;
import org.springframework.ide.vscode.boot.common.RelaxedNameConfig;
import org.springframework.ide.vscode.boot.configurationmetadata.Deprecation;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
import org.springframework.ide.vscode.boot.java.links.JavaElementLocationProvider;
import org.springframework.ide.vscode.boot.java.links.SourceLinkFactory;
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
import org.springframework.ide.vscode.boot.metadata.IndexNavigator;
@@ -41,10 +43,13 @@ import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.BeanPropertyN
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.EnumCaseMode;
import org.springframework.ide.vscode.boot.metadata.types.TypedProperty;
import org.springframework.ide.vscode.boot.metadata.util.PropertyDocUtils;
import org.springframework.ide.vscode.boot.properties.hover.PropertiesDefinitionCalculator;
import org.springframework.ide.vscode.commons.java.IField;
import org.springframework.ide.vscode.commons.java.IJavaElement;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.IMember;
import org.springframework.ide.vscode.commons.java.IMethod;
import org.springframework.ide.vscode.commons.java.IType;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.languageserver.completion.LazyProposalApplier;
@@ -97,10 +102,13 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon
public final TypeUtil typeUtil;
public ApplicationYamlAssistContext(YamlDocument doc, int documentSelector, YamlPath contextPath, TypeUtil typeUtil, RelaxedNameConfig conf) {
public final JavaElementLocationProvider javaElementLocationProvider;
public ApplicationYamlAssistContext(YamlDocument doc, int documentSelector, YamlPath contextPath, TypeUtil typeUtil, RelaxedNameConfig conf, JavaElementLocationProvider javaElementLocationProvider) {
super(doc, documentSelector, contextPath);
this.typeUtil = typeUtil;
this.conf = conf;
this.javaElementLocationProvider = javaElementLocationProvider;
}
/**
@@ -130,8 +138,8 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon
*/
protected abstract Type getType();
public static ApplicationYamlAssistContext subdocument(YamlDocument doc, int documentSelector, FuzzyMap<PropertyInfo> index, PropertyCompletionFactory completionFactory, TypeUtil typeUtil, RelaxedNameConfig conf) {
return new IndexContext(doc, documentSelector, YamlPath.EMPTY, IndexNavigator.with(index), completionFactory, typeUtil, conf);
public static ApplicationYamlAssistContext subdocument(YamlDocument doc, int documentSelector, FuzzyMap<PropertyInfo> index, PropertyCompletionFactory completionFactory, TypeUtil typeUtil, RelaxedNameConfig conf, JavaElementLocationProvider javaElementLocationProvider) {
return new IndexContext(doc, documentSelector, YamlPath.EMPTY, IndexNavigator.with(index), completionFactory, typeUtil, conf, javaElementLocationProvider);
}
private static class TypeContext extends ApplicationYamlAssistContext {
@@ -142,8 +150,8 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon
private HintProvider hints;
public TypeContext(ApplicationYamlAssistContext parent, YamlPath contextPath, Type type,
PropertyCompletionFactory completionFactory, TypeUtil typeUtil, RelaxedNameConfig conf, HintProvider hints) {
super(parent.getDocument(), parent.documentSelector, contextPath, typeUtil, conf);
PropertyCompletionFactory completionFactory, TypeUtil typeUtil, RelaxedNameConfig conf, HintProvider hints, JavaElementLocationProvider javaElementLocationProvider) {
super(parent.getDocument(), parent.documentSelector, contextPath, typeUtil, conf, javaElementLocationProvider);
this.parent = parent;
this.completionFactory = completionFactory;
this.type = type;
@@ -341,7 +349,7 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon
private AbstractYamlAssistContext contextWith(YamlPathSegment s, Type nextType) {
if (nextType!=null) {
return new TypeContext(this, contextPath.append(s), nextType, completionFactory, typeUtil, conf,
new YamlPath(s).traverse(hints));
new YamlPath(s).traverse(hints), javaElementLocationProvider);
}
return null;
}
@@ -382,6 +390,42 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon
return null;
}
@Override
public List<Location> getDefinitionsForPropertyKey() {
if (parent instanceof IndexContext) {
//this context is in fact an 'alias' of its parent, representing the
// point in the context hierarchy where a we transition from navigating
// the index to navigating type/bean properties
return parent.getDefinitionsForPropertyKey();
} else {
String propName = contextPath.getBeanPropertyName();
Type parentType = parent.getType();
IJavaProject javaProject = typeUtil.getJavaProject();
Type keyType = typeUtil.getMapKeyType(parentType);
if (keyType != null) {
String keyValue = contextPath.getLastSegment().toPropString();
return PropertiesDefinitionCalculator.getValueDefinitionLocations(javaElementLocationProvider, typeUtil, keyType, keyValue);
} else {
IType javaType = javaProject.findType(parentType.getErasure());
if (javaType != null) {
IMethod method = PropertiesDefinitionCalculator.getPropertyMethod(javaType, propName);
if (method != null) {
Location location = javaElementLocationProvider.findLocation(javaProject, method);
if (location != null) {
return ImmutableList.of(location);
}
}
}
}
return ImmutableList.of();
}
}
@Override
public List<Location> getDefinitionsForPropertyValue(DocumentRegion valueRegion) {
return PropertiesDefinitionCalculator.getValueDefinitionLocations(javaElementLocationProvider, typeUtil, type, valueRegion.toString().trim());
}
}
private static class IndexContext extends ApplicationYamlAssistContext {
@@ -390,8 +434,8 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon
PropertyCompletionFactory completionFactory;
public IndexContext(YamlDocument doc, int documentSelector, YamlPath contextPath, IndexNavigator indexNav,
PropertyCompletionFactory completionFactory, TypeUtil typeUtil, RelaxedNameConfig conf) {
super(doc, documentSelector, contextPath, typeUtil, conf);
PropertyCompletionFactory completionFactory, TypeUtil typeUtil, RelaxedNameConfig conf, JavaElementLocationProvider javaElementLocationProvider) {
super(doc, documentSelector, contextPath, typeUtil, conf, javaElementLocationProvider);
this.indexNav = indexNav;
this.completionFactory = completionFactory;
}
@@ -467,11 +511,11 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon
}
}
if (subIndex.getExtensionCandidate()!=null) {
return new IndexContext(getDocument(), documentSelector, contextPath.append(s), subIndex, completionFactory, typeUtil, conf);
return new IndexContext(getDocument(), documentSelector, contextPath.append(s), subIndex, completionFactory, typeUtil, conf, javaElementLocationProvider);
} else if (subIndex.getExactMatch()!=null) {
IndexContext asIndexContext = new IndexContext(getDocument(), documentSelector, contextPath.append(s), subIndex, completionFactory, typeUtil, conf);
IndexContext asIndexContext = new IndexContext(getDocument(), documentSelector, contextPath.append(s), subIndex, completionFactory, typeUtil, conf, javaElementLocationProvider);
PropertyInfo prop = subIndex.getExactMatch();
return new TypeContext(asIndexContext, contextPath.append(s), TypeParser.parse(prop.getType()), completionFactory, typeUtil, conf, prop.getHints(typeUtil, true));
return new TypeContext(asIndexContext, contextPath.append(s), TypeParser.parse(prop.getType()), completionFactory, typeUtil, conf, prop.getHints(typeUtil, true), javaElementLocationProvider);
}
}
//Unsuported navigation => no context for assist
@@ -502,6 +546,23 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon
return null;
}
@Override
public List<Location> getDefinitionsForPropertyKey() {
PropertyInfo prop = indexNav.getExactMatch();
if (prop != null) {
IJavaProject project = typeUtil.getJavaProject();
Collection<IMember> elements = PropertiesDefinitionCalculator.getPropertyJavaElement(project, prop);
return PropertiesDefinitionCalculator.getLocations(javaElementLocationProvider, project, elements);
}
return ImmutableList.of();
}
@Override
public List<Location> getDefinitionsForPropertyValue(DocumentRegion valueRegion) {
// Shouldn't be reaching this point. TypeContext should be supplying the value definition
return super.getDefinitionsForPropertyValue(valueRegion);
}
@Override
public Renderable getHoverInfo(YamlPathSegment lastSegment) {
// TODO Auto-generated method stub
@@ -521,11 +582,11 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon
return null;
}
public static YamlAssistContext global(YamlDocument doc, final FuzzyMap<PropertyInfo> index, final PropertyCompletionFactory completionFactory, final TypeUtil typeUtil, final RelaxedNameConfig conf) {
public static YamlAssistContext global(YamlDocument doc, final FuzzyMap<PropertyInfo> index, final PropertyCompletionFactory completionFactory, final TypeUtil typeUtil, final RelaxedNameConfig conf, JavaElementLocationProvider javaElementLocationProvider) {
return new TopLevelAssistContext() {
@Override
protected YamlAssistContext getDocumentContext(int documentSelector) {
return subdocument(doc, documentSelector, index, completionFactory, typeUtil, conf);
return subdocument(doc, documentSelector, index, completionFactory, typeUtil, conf, javaElementLocationProvider);
}
@Override

View File

@@ -1,3 +1,13 @@
/*******************************************************************************
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.bootiful;
import org.eclipse.lsp4j.TextDocumentIdentifier;
@@ -6,12 +16,14 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ide.vscode.boot.app.BootLanguageServerParams;
import org.springframework.ide.vscode.boot.editor.harness.PropertyIndexHarness;
import org.springframework.ide.vscode.boot.java.links.JavaDocumentUriProvider;
import org.springframework.ide.vscode.boot.java.links.SourceLinkFactory;
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
import org.springframework.ide.vscode.boot.java.utils.SpringLiveHoverWatchdog;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtilProvider;
import org.springframework.ide.vscode.boot.test.DefinitionLinkAsserts;
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.util.SimpleLanguageServer;
@@ -64,4 +76,8 @@ import org.springframework.ide.vscode.project.harness.MockRunningAppProvider;
return SourceLinkFactory.NO_SOURCE_LINKS;
}
@Bean DefinitionLinkAsserts definitionLinkAsserts(JavaDocumentUriProvider javaDocumentUriProvider, CompilationUnitCache cuCache) {
return new DefinitionLinkAsserts(javaDocumentUriProvider, cuCache);
}
}

View File

@@ -13,33 +13,18 @@ package org.springframework.ide.vscode.boot.test;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.ide.vscode.boot.properties.reconcile.ApplicationPropertiesProblemType.PROP_DUPLICATE_KEY;
import static org.springframework.ide.vscode.boot.test.DefinitionLinkAsserts.field;
import static org.springframework.ide.vscode.boot.test.DefinitionLinkAsserts.method;
import static org.springframework.ide.vscode.languageserver.testharness.ClasspathTestUtil.getOutputFolder;
import static org.springframework.ide.vscode.languageserver.testharness.TestAsserts.assertContains;
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.EnumConstantDeclaration;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Range;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -51,24 +36,17 @@ import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
import org.springframework.ide.vscode.boot.bootiful.PropertyEditorTestConf;
import org.springframework.ide.vscode.boot.editor.harness.AbstractPropsEditorTest;
import org.springframework.ide.vscode.boot.editor.harness.StyledStringMatcher;
import org.springframework.ide.vscode.boot.java.links.JavaDocumentUriProvider;
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
import org.springframework.ide.vscode.boot.metadata.CachingValueProvider;
import org.springframework.ide.vscode.boot.metadata.PropertiesLoader;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.IType;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import org.springframework.ide.vscode.languageserver.testharness.Editor;
import org.springframework.ide.vscode.project.harness.ProjectsHarness.ProjectCustomizer;
import org.springframework.test.context.junit4.SpringRunner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.Files;
/**
@@ -82,13 +60,7 @@ import com.google.common.io.Files;
public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
@Autowired
CompilationUnitCache cuCache;
@Autowired
SimpleTextDocumentService docService;
@Autowired
JavaDocumentUriProvider javaDocumentUriProvider;
private DefinitionLinkAsserts definitionLinkAsserts;
@Configuration static class TestConf {
@Bean LanguageId defaultLanguageId() {
@@ -317,258 +289,20 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
"flyway.init-sqls=a,b,c\n"
);
assertLinkTargets(editor, "server", p,
definitionLinkAsserts.assertLinkTargets(editor, "server", p,
method("org.springframework.boot.autoconfigure.web.ServerProperties", "setPort", "java.lang.Integer"));
assertLinkTargets(editor, "data", p,
definitionLinkAsserts.assertLinkTargets(editor, "data", p,
method("org.springframework.boot.autoconfigure.jdbc.DataSourceConfigMetadata", "hikariDataSource"),
method("org.springframework.boot.autoconfigure.jdbc.DataSourceConfigMetadata", "tomcatDataSource"),
method("org.springframework.boot.autoconfigure.jdbc.DataSourceConfigMetadata", "dbcpDataSource")
);
assertLinkTargets(editor, "flyway", p, method("org.springframework.boot.autoconfigure.flyway.FlywayProperties", "setInitSqls", "java.util.List"));
definitionLinkAsserts.assertLinkTargets(editor, "flyway", p, method("org.springframework.boot.autoconfigure.flyway.FlywayProperties", "setInitSqls", "java.util.List"));
System.out.println("<<< testHyperlinkTargets");
}
void assertLinkTargets(Editor editor, String hoverOver, IJavaProject project, JavaMethod... methods) throws Exception {
Set<Location> expectedLocations = Arrays.stream(methods).map(method -> {
try {
return getLocation(project, method);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}).collect(Collectors.toSet());
editor.assertLinkTargets(hoverOver, expectedLocations);
}
void assertLinkTargets(Editor editor, String hoverOver, IJavaProject project, String typeFqName) throws Exception {
Location expectedLocation = getLocation(project, typeFqName);
System.out.println("Expected Location: " + expectedLocation);
editor.assertLinkTargets(hoverOver, ImmutableSet.of(expectedLocation));
}
void assertLinkTargets(Editor editor, String hoverOver, IJavaProject project, JavaField field) throws Exception {
Location expectedLocation = getLocation(project, field);
System.out.println("Expected Location: " + expectedLocation);
editor.assertLinkTargets(hoverOver, ImmutableSet.of(expectedLocation));
}
static private JavaMethod method(String fqClassName, String methodName, String... params) {
return new JavaMethod(fqClassName, methodName, params);
}
static private JavaField field(String fqClassName, String name) {
return new JavaField(fqClassName, name);
}
static class JavaMethod {
public final String fqName;
public final String methodName;
public final String[] params;
public JavaMethod(String fqClassName, String methodName, String... params) {
this.fqName = fqClassName;
this.methodName = methodName;
this.params = params;
}
@Override
public String toString() {
return "JavaMethod [fqName=" + fqName + ", methodName=" + methodName + ", params=" + Arrays.toString(params)
+ "]";
}
}
static class JavaField {
public final String fqName;
public final String fieldName;
public JavaField(String fqName, String fieldName) {
super();
this.fqName = fqName;
this.fieldName = fieldName;
}
@Override
public String toString() {
return "JavaField [fqName=" + fqName + ", fieldName=" + fieldName + "]";
}
}
private Location getLocation(IJavaProject project, String fqName) throws Exception {
Location loc = new Location();
Optional<URL> sourceUrl = SourceLinks.source(project, fqName);
if (sourceUrl.isPresent()) {
URI docUri = javaDocumentUriProvider.docUri(project, fqName);
loc.setUri(docUri.toString());
String typeName = fqName.substring(fqName.lastIndexOf('.') + 1);
URI sourceUri = sourceUrl.get().toURI();
Range r = cuCache.withCompilationUnit(project, sourceUri, (cu) -> {
try {
TextDocument doc = new TextDocument(sourceUrl.get().toString(), LanguageId.JAVA);
doc.setText(cuCache.fetchContent(sourceUri));
AtomicReference<Range> range = new AtomicReference<>(null);
cu.accept(new ASTVisitor() {
private boolean proceessTypeNode(TextDocument doc, String typeName,
AtomicReference<Range> range, AbstractTypeDeclaration node) {
SimpleName nameNode = node.getName();
if (nameNode.getIdentifier().equals(typeName)) {
try {
range.set(doc.toRange(nameNode.getStartPosition(), nameNode.getLength()));
return false;
} catch (BadLocationException e) {
throw new IllegalStateException(e);
}
}
return true;
}
@Override
public boolean visit(TypeDeclaration node) {
return proceessTypeNode(doc, typeName, range, node);
}
@Override
public boolean visit(EnumDeclaration node) {
return proceessTypeNode(doc, typeName, range, node);
}
});
return range.get();
} catch (Exception e) {
throw new IllegalStateException(e);
}
});
if (r == null) {
throw new IllegalStateException("Couldn't find " + fqName);
}
loc.setRange(r);
}
return loc;
}
private Location getLocation(IJavaProject project, JavaMethod method) throws Exception {
Location loc = new Location();
Optional<URL> sourceUrl = SourceLinks.source(project, method.fqName);
if (sourceUrl.isPresent()) {
URI docUri = javaDocumentUriProvider.docUri(project, method.fqName);
loc.setUri(docUri.toString());
URI sourceUri = sourceUrl.get().toURI();
Range r = cuCache.withCompilationUnit(project, sourceUri, (cu) -> {
try {
AtomicReference<Range> range = new AtomicReference<>(null);
TextDocument doc = new TextDocument(sourceUrl.get().toString(), LanguageId.JAVA);
doc.setText(cuCache.fetchContent(sourceUri));
cu.accept(new ASTVisitor() {
@Override
public boolean visit(MethodDeclaration node) {
SimpleName nameNode = node.getName();
if (nameNode.getIdentifier().equals(method.methodName)) {
if (node.parameters().size() != method.params.length) {
return false;
}
int i = 0;
for (Object _p : node.parameters()) {
if (_p instanceof SingleVariableDeclaration) {
SingleVariableDeclaration p = (SingleVariableDeclaration) _p;
String fqName = p.getType().resolveBinding().getErasure().getQualifiedName();
if (!fqName.equals(method.params[i++])) {
return false;
}
} else {
return false;
}
}
try {
range.set(doc.toRange(nameNode.getStartPosition(), nameNode.getLength()));
} catch (BadLocationException e) {
throw new IllegalStateException(e);
}
}
return false;
}
});
return range.get();
} catch (Exception e) {
throw new IllegalStateException(e);
}
});
if (r == null) {
throw new IllegalStateException("Couldn't find " + method);
}
loc.setRange(r);
}
return loc;
}
private Location getLocation(IJavaProject project, JavaField field) throws Exception {
Location loc = new Location();
Optional<URL> sourceUrl = SourceLinks.source(project, field.fqName);
if (sourceUrl.isPresent()) {
URI sourceUri = sourceUrl.get().toURI();
URI docUri = javaDocumentUriProvider.docUri(project, field.fqName);
loc.setUri(docUri.toString());
Range r = cuCache.withCompilationUnit(project, sourceUri, (cu) -> {
try {
AtomicReference<Range> range = new AtomicReference<>(null);
TextDocument doc = new TextDocument(sourceUrl.get().toString(), LanguageId.JAVA);
doc.setText(cuCache.fetchContent(sourceUri));
cu.accept(new ASTVisitor() {
boolean foundType = false;
@Override
public boolean visit(EnumConstantDeclaration node) {
if (foundType) {
SimpleName nameNode = node.getName();
if (nameNode.getIdentifier().equals(field.fieldName)) {
try {
range.set(doc.toRange(nameNode.getStartPosition(), nameNode.getLength()));
} catch (BadLocationException e) {
throw new IllegalStateException(e);
}
}
}
return true;
}
@Override
public boolean visit(EnumDeclaration node) {
if (node.getName().getIdentifier()
.equals(field.fqName.substring(field.fqName.lastIndexOf('.') + 1))) {
foundType = true;
return true;
}
return false;
}
});
return range.get();
} catch (Exception e) {
throw new IllegalStateException(e);
}
});
if (r == null) {
throw new IllegalStateException("Couldn't find " + field);
}
loc.setRange(r);
}
return loc;
}
@Test public void testHyperlinkTargetsLoggingLevel() throws Exception {
System.out.println(">>> testHyperlinkTargetsLoggingLevel");
@@ -580,7 +314,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
"logging.level.com.acme=INFO\n"
);
assertLinkTargets(editor, "level", p, "org.springframework.boot.logging.LoggingApplicationListener");
definitionLinkAsserts.assertLinkTargets(editor, "level", p, "org.springframework.boot.logging.LoggingApplicationListener");
System.out.println("<<< testHyperlinkTargetsLoggingLevel");
}
@@ -1702,7 +1436,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
"spring.data.mongodb.field-naming-strategy=org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy\n" +
"#more stuff"
);
assertLinkTargets(editor, "org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy", project, "org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy");
definitionLinkAsserts.assertLinkTargets(editor, "org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy", project, "org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy");
//Linking should also work for types that aren't valid based on the constraints
@@ -1713,7 +1447,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
"spring.data.mongodb.field-naming-strategy=java.lang.String\n" +
"#more stuff"
);
assertLinkTargets(editor, "java.lang.String", project, "java.lang.String");
definitionLinkAsserts.assertLinkTargets(editor, "java.lang.String", project, "java.lang.String");
// Instead of java.lang.String
editor = newEditor(
@@ -1721,7 +1455,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
"spring.data.mongodb.field-naming-strategy=org.springframework.core.io.Resource\n" +
"#more stuff"
);
assertLinkTargets(editor, "org.springframework.core.io.Resource", project, "org.springframework.core.io.Resource");
definitionLinkAsserts.assertLinkTargets(editor, "org.springframework.core.io.Resource", project, "org.springframework.core.io.Resource");
}
@@ -1894,12 +1628,12 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
editor = newEditor(
"my.background: RED"
);
assertLinkTargets(editor, "RED", project, field("demo.Color", "RED"));
definitionLinkAsserts.assertLinkTargets(editor, "RED", project, field("demo.Color", "RED"));
editor = newEditor(
"my.background=red"
);
assertLinkTargets(editor, "red", project, field("demo.Color", "RED"));
definitionLinkAsserts.assertLinkTargets(editor, "red", project, field("demo.Color", "RED"));
}
@Test public void testEnumInPojoField() throws Exception {
@@ -1911,8 +1645,8 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
editor = newEditor(
"my.screen.background=green"
);
assertLinkTargets(editor, "background", project, method("com.example.demo.MyProperties$Screen", "getScreen"));
assertLinkTargets(editor, "green", project, field("com.example.demo.Color", "GREEN"));
definitionLinkAsserts.assertLinkTargets(editor, "background", project, method("com.example.demo.MyProperties$Screen", "getScreen"));
definitionLinkAsserts.assertLinkTargets(editor, "green", project, field("com.example.demo.Color", "GREEN"));
}
@Test public void testNoHoverForUnrecognizedProperty() throws Exception {

View File

@@ -13,6 +13,8 @@ package org.springframework.ide.vscode.boot.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.ide.vscode.boot.test.DefinitionLinkAsserts.field;
import static org.springframework.ide.vscode.boot.test.DefinitionLinkAsserts.method;
import static org.springframework.ide.vscode.languageserver.testharness.Editor.INDENTED_COMPLETION;
import java.time.Duration;
@@ -20,10 +22,10 @@ import java.util.Optional;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.Diagnostic;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@@ -34,6 +36,7 @@ import org.springframework.ide.vscode.boot.editor.harness.StyledStringMatcher;
import org.springframework.ide.vscode.boot.metadata.CachingValueProvider;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject;
import org.springframework.ide.vscode.commons.util.RunnableWithException;
import org.springframework.ide.vscode.commons.util.StringUtil;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
@@ -52,6 +55,9 @@ import org.springframework.test.context.junit4.SpringRunner;
@Import(PropertyEditorTestConf.class)
public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
@Autowired
private DefinitionLinkAsserts definitionLinkAsserts;
@Configuration static class TestConf {
@Bean LanguageId defaultLanguageId() {
return LanguageId.BOOT_PROPERTIES_YAML;
@@ -384,7 +390,7 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
}
@Ignore @Test public void testUserDefinedHoversandLinkTargets() throws Exception {
@Ignore @Test public void testUserDefinedHovers() throws Exception {
useProject(createPredefinedMavenProject("enums-boot-1.3.2-app"));
data("foo.link-tester", "demo.LinkTestSubject", null, "for testing different Pojo link cases");
Editor editor = newEditor(
@@ -407,12 +413,30 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
editor.assertHoverContains("name", "Set the name"); // javadoc from setter
editor.assertHoverContains("next", "Get the next"); // javadoc from getter
editor.assertLinkTargets("data", "demo.FooProperties.setdata(ColorData)");
editor.assertLinkTargets("wavelen", "demo.ColorData.setWavelen(double)");
}
@Ignore @Test public void testHyperlinkTargets() throws Exception {
@Test public void testUserDefinedLinkTargets() throws Exception {
MavenJavaProject project = createPredefinedMavenProject("enums-boot-1.3.2-app");
useProject(project);
data("foo.link-tester", "demo.LinkTestSubject", null, "for testing different Pojo link cases");
Editor editor = newEditor(
"#A comment at the start\n" +
"foo:\n" +
" data:\n" +
" wavelen: 666\n" +
" name: foo\n" +
" next: green\n" +
" link-tester:\n" +
" has-it-all: nice\n" +
" strange: weird\n" +
" getter-only: getme\n"
);
definitionLinkAsserts.assertLinkTargets(editor, "data", project, method("demo.FooProperties", "setData", "demo.ColorData"));
definitionLinkAsserts.assertLinkTargets(editor, "wavelen", project, method("demo.ColorData", "setWavelen", "double"));
}
@Test public void testHyperlinkTargets() throws Exception {
IJavaProject p = createPredefinedMavenProject("tricky-getters-boot-1.3.1-app");
useProject(p);
@@ -426,16 +450,16 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
" init-sqls: a,b,c\n"
);
editor.assertLinkTargets("port",
"org.springframework.boot.autoconfigure.web.ServerProperties.setPort(Integer)"
definitionLinkAsserts.assertLinkTargets(editor, "port", p,
method("org.springframework.boot.autoconfigure.web.ServerProperties", "setPort", "java.lang.Integer")
);
editor.assertLinkTargets("login-",
"org.springframework.boot.autoconfigure.jdbc.DataSourceConfigMetadata.hikariDataSource()",
"org.springframework.boot.autoconfigure.jdbc.DataSourceConfigMetadata.tomcatDataSource()",
"org.springframework.boot.autoconfigure.jdbc.DataSourceConfigMetadata.dbcpDataSource()"
definitionLinkAsserts.assertLinkTargets(editor, "login-", p,
method("org.springframework.boot.autoconfigure.jdbc.DataSourceConfigMetadata", "hikariDataSource"),
method("org.springframework.boot.autoconfigure.jdbc.DataSourceConfigMetadata", "tomcatDataSource"),
method("org.springframework.boot.autoconfigure.jdbc.DataSourceConfigMetadata", "dbcpDataSource")
);
editor.assertLinkTargets("init-sql",
"org.springframework.boot.autoconfigure.flyway.FlywayProperties.setInitSqls(List<String>)");
definitionLinkAsserts.assertLinkTargets(editor, "init-sql", p,
method("org.springframework.boot.autoconfigure.flyway.FlywayProperties", "setInitSqls", "java.util.List"));
}
@Test public void testReconcile() throws Exception {
@@ -3552,9 +3576,10 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
);
}
@Ignore @Test public void testClassReferenceInValueLink() throws Exception {
@Test public void testClassReferenceInValueLink() throws Exception {
Editor editor;
useProject(createPredefinedMavenProject("empty-boot-1.3.0-with-mongo"));
MavenJavaProject project = createPredefinedMavenProject("empty-boot-1.3.0-with-mongo");
useProject(project);
editor = newEditor(
"spring:\n" +
@@ -3562,7 +3587,7 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
" mongodb:\n" +
" field-naming-strategy: org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy\n"
);
editor.assertLinkTargets("org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy", "org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy");
definitionLinkAsserts.assertLinkTargets(editor, "org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy", project, "org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy");
editor = newEditor(
"spring:\n" +
@@ -3571,7 +3596,7 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
" field-naming-strategy:\n" +
" org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy\n"
);
editor.assertLinkTargets("org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy", "org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy");
definitionLinkAsserts.assertLinkTargets(editor, "org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy", project, "org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy");
//Linking should also work for types that aren't valid based on the constraints
editor = newEditor(
@@ -3581,7 +3606,7 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
" field-naming-strategy: java.lang.String\n" +
"#more stuff"
);
editor.assertLinkTargets("java.lang.String", "java.lang.String");
definitionLinkAsserts.assertLinkTargets(editor, "java.lang.String", project, "java.lang.String");
}
@Test public void test_STS_3335_reconcile_list_nested_in_Map_of_String() throws Exception {
@@ -3716,27 +3741,29 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
editor.assertHoverContains("RED", "Hot and delicious");
}
@Ignore @Test public void testHyperLinkEnumValue() throws Exception {
@Test public void testHyperLinkEnumValue() throws Exception {
Editor editor;
useProject(createPredefinedMavenProject("enums-boot-1.3.2-app"));
MavenJavaProject project = createPredefinedMavenProject("enums-boot-1.3.2-app");
useProject(project);
data("my.background", "demo.Color", null, "Color to use as default background.");
editor = newEditor(
"my:\n" +
" background: RED"
);
editor.assertLinkTargets("RED", "demo.Color.RED");
definitionLinkAsserts.assertLinkTargets(editor, "RED", project, field("demo.Color", "RED"));
editor = newEditor(
"my:\n" +
" background: red"
);
editor.assertLinkTargets("red", "demo.Color.RED");
definitionLinkAsserts.assertLinkTargets(editor, "red", project, field("demo.Color", "RED"));
}
@Ignore @Test public void testHyperLinkEnumValueInMapKey() throws Exception {
@Test public void testHyperLinkEnumValueInMapKey() throws Exception {
Editor editor;
useProject(createPredefinedMavenProject("enums-boot-1.3.2-app"));
MavenJavaProject project = createPredefinedMavenProject("enums-boot-1.3.2-app");
useProject(project);
data("my.color.map", "java.util.Map<demo.Color,java.lang.String>", null, "Pretty names for the colors.");
editor = newEditor(
@@ -3746,8 +3773,8 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
" RED: Rood\n" +
" green: Groen\n"
);
editor.assertLinkTargets("RED", "demo.Color.RED");
editor.assertLinkTargets("green", "demo.Color.GREEN");
definitionLinkAsserts.assertLinkTargets(editor, "RED", project, field("demo.Color", "RED"));
definitionLinkAsserts.assertLinkTargets(editor, "green", project, field("demo.Color", "GREEN"));
editor = newEditor(
"spring:\n" +
@@ -3755,8 +3782,8 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
" serialization:\n" +
" INDENT_OUTPUT: true"
);
editor.assertLinkTargets("INDENT_OUTPUT",
"com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT"
definitionLinkAsserts.assertLinkTargets(editor, "INDENT_OUTPUT", project,
field("com.fasterxml.jackson.databind.SerializationFeature", "INDENT_OUTPUT")
);
editor = newEditor(
@@ -3765,8 +3792,8 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
" serialization:\n" +
" indent-output: true"
);
editor.assertLinkTargets("indent-output",
"com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT"
definitionLinkAsserts.assertLinkTargets(editor, "indent-output", project,
field("com.fasterxml.jackson.databind.SerializationFeature", "INDENT_OUTPUT")
);
}

View File

@@ -0,0 +1,293 @@
/*******************************************************************************
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.test;
import java.net.URI;
import java.net.URL;
import java.util.Arrays;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.EnumConstantDeclaration;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Range;
import org.springframework.ide.vscode.boot.java.links.JavaDocumentUriProvider;
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import org.springframework.ide.vscode.languageserver.testharness.Editor;
import com.google.common.collect.ImmutableSet;
public class DefinitionLinkAsserts {
private JavaDocumentUriProvider javaDocumentUriProvider;
private CompilationUnitCache cuCache;
public static JavaMethod method(String fqClassName, String methodName, String... params) {
return new JavaMethod(fqClassName, methodName, params);
}
public static JavaField field(String fqClassName, String name) {
return new JavaField(fqClassName, name);
}
public static class JavaMethod {
public final String fqName;
public final String methodName;
public final String[] params;
public JavaMethod(String fqClassName, String methodName, String... params) {
this.fqName = fqClassName;
this.methodName = methodName;
this.params = params;
}
@Override
public String toString() {
return "JavaMethod [fqName=" + fqName + ", methodName=" + methodName + ", params=" + Arrays.toString(params)
+ "]";
}
}
public static class JavaField {
public final String fqName;
public final String fieldName;
public JavaField(String fqName, String fieldName) {
super();
this.fqName = fqName;
this.fieldName = fieldName;
}
@Override
public String toString() {
return "JavaField [fqName=" + fqName + ", fieldName=" + fieldName + "]";
}
}
public DefinitionLinkAsserts(JavaDocumentUriProvider javaDocumentUriProvider, CompilationUnitCache cuCache) {
this.javaDocumentUriProvider = javaDocumentUriProvider;
this.cuCache = cuCache;
}
public void assertLinkTargets(Editor editor, String hoverOver, IJavaProject project, JavaMethod... methods) throws Exception {
Set<Location> expectedLocations = Arrays.stream(methods).map(method -> {
try {
return getLocation(project, method);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}).collect(Collectors.toSet());
editor.assertLinkTargets(hoverOver, expectedLocations);
}
public void assertLinkTargets(Editor editor, String hoverOver, IJavaProject project, String typeFqName) throws Exception {
Location expectedLocation = getLocation(project, typeFqName);
System.out.println("Expected Location: " + expectedLocation);
editor.assertLinkTargets(hoverOver, ImmutableSet.of(expectedLocation));
}
public void assertLinkTargets(Editor editor, String hoverOver, IJavaProject project, JavaField field) throws Exception {
Location expectedLocation = getLocation(project, field);
System.out.println("Expected Location: " + expectedLocation);
editor.assertLinkTargets(hoverOver, ImmutableSet.of(expectedLocation));
}
private Location getLocation(IJavaProject project, String fqName) throws Exception {
Location loc = new Location();
Optional<URL> sourceUrl = SourceLinks.source(project, fqName);
if (sourceUrl.isPresent()) {
URI docUri = javaDocumentUriProvider.docUri(project, fqName);
loc.setUri(docUri.toString());
String typeName = fqName.substring(fqName.lastIndexOf('.') + 1);
URI sourceUri = sourceUrl.get().toURI();
Range r = cuCache.withCompilationUnit(project, sourceUri, (cu) -> {
try {
TextDocument doc = new TextDocument(sourceUrl.get().toString(), LanguageId.JAVA);
doc.setText(cuCache.fetchContent(sourceUri));
AtomicReference<Range> range = new AtomicReference<>(null);
cu.accept(new ASTVisitor() {
private boolean proceessTypeNode(TextDocument doc, String typeName,
AtomicReference<Range> range, AbstractTypeDeclaration node) {
SimpleName nameNode = node.getName();
if (nameNode.getIdentifier().equals(typeName)) {
try {
range.set(doc.toRange(nameNode.getStartPosition(), nameNode.getLength()));
return false;
} catch (BadLocationException e) {
throw new IllegalStateException(e);
}
}
return true;
}
@Override
public boolean visit(TypeDeclaration node) {
return proceessTypeNode(doc, typeName, range, node);
}
@Override
public boolean visit(EnumDeclaration node) {
return proceessTypeNode(doc, typeName, range, node);
}
});
return range.get();
} catch (Exception e) {
throw new IllegalStateException(e);
}
});
if (r == null) {
throw new IllegalStateException("Couldn't find " + fqName);
}
loc.setRange(r);
}
return loc;
}
private Location getLocation(IJavaProject project, JavaMethod method) throws Exception {
Location loc = new Location();
Optional<URL> sourceUrl = SourceLinks.source(project, method.fqName);
if (sourceUrl.isPresent()) {
URI docUri = javaDocumentUriProvider.docUri(project, method.fqName);
loc.setUri(docUri.toString());
URI sourceUri = sourceUrl.get().toURI();
Range r = cuCache.withCompilationUnit(project, sourceUri, (cu) -> {
try {
AtomicReference<Range> range = new AtomicReference<>(null);
TextDocument doc = new TextDocument(sourceUrl.get().toString(), LanguageId.JAVA);
doc.setText(cuCache.fetchContent(sourceUri));
cu.accept(new ASTVisitor() {
@Override
public boolean visit(MethodDeclaration node) {
SimpleName nameNode = node.getName();
if (nameNode.getIdentifier().equals(method.methodName)) {
if (node.parameters().size() != method.params.length) {
return false;
}
int i = 0;
for (Object _p : node.parameters()) {
if (_p instanceof SingleVariableDeclaration) {
SingleVariableDeclaration p = (SingleVariableDeclaration) _p;
String fqName = p.getType().resolveBinding().getErasure().getQualifiedName();
if (!fqName.equals(method.params[i++])) {
return false;
}
} else {
return false;
}
}
try {
range.set(doc.toRange(nameNode.getStartPosition(), nameNode.getLength()));
} catch (BadLocationException e) {
throw new IllegalStateException(e);
}
}
return false;
}
});
return range.get();
} catch (Exception e) {
throw new IllegalStateException(e);
}
});
if (r == null) {
throw new IllegalStateException("Couldn't find " + method);
}
loc.setRange(r);
}
return loc;
}
private Location getLocation(IJavaProject project, JavaField field) throws Exception {
Location loc = new Location();
Optional<URL> sourceUrl = SourceLinks.source(project, field.fqName);
if (sourceUrl.isPresent()) {
URI sourceUri = sourceUrl.get().toURI();
URI docUri = javaDocumentUriProvider.docUri(project, field.fqName);
loc.setUri(docUri.toString());
Range r = cuCache.withCompilationUnit(project, sourceUri, (cu) -> {
try {
AtomicReference<Range> range = new AtomicReference<>(null);
TextDocument doc = new TextDocument(sourceUrl.get().toString(), LanguageId.JAVA);
doc.setText(cuCache.fetchContent(sourceUri));
cu.accept(new ASTVisitor() {
boolean foundType = false;
@Override
public boolean visit(EnumConstantDeclaration node) {
if (foundType) {
SimpleName nameNode = node.getName();
if (nameNode.getIdentifier().equals(field.fieldName)) {
try {
range.set(doc.toRange(nameNode.getStartPosition(), nameNode.getLength()));
} catch (BadLocationException e) {
throw new IllegalStateException(e);
}
}
}
return true;
}
@Override
public boolean visit(EnumDeclaration node) {
if (node.getName().getIdentifier()
.equals(field.fqName.substring(field.fqName.lastIndexOf('.') + 1))) {
foundType = true;
return true;
}
return false;
}
});
return range.get();
} catch (Exception e) {
throw new IllegalStateException(e);
}
});
if (r == null) {
throw new IllegalStateException("Couldn't find " + field);
}
loc.setRange(r);
}
return loc;
}
}