Merge application-yaml into boot-properties project
This commit is contained in:
@@ -19,11 +19,11 @@ import org.springframework.ide.vscode.application.properties.metadata.completion
|
||||
import org.springframework.ide.vscode.application.properties.metadata.completions.RelaxedNameConfig;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtilProvider;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap;
|
||||
import org.springframework.ide.vscode.application.yaml.completions.ApplicationYamlAssistContext;
|
||||
import org.springframework.ide.vscode.application.yaml.reconcile.ApplicationYamlReconcileEngine;
|
||||
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.completion.VscodeCompletionEngineAdapter;
|
||||
import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfoProvider;
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.eclipse.lsp4j.ServerCapabilities;
|
||||
import org.eclipse.lsp4j.TextDocumentSyncKind;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.SpringPropertyIndexProvider;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtilProvider;
|
||||
import org.springframework.ide.vscode.boot.BootPropertiesLanguageServer;
|
||||
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;
|
||||
@@ -27,11 +28,11 @@ import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocu
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.TextDocument;
|
||||
|
||||
/**
|
||||
* Language Server for Spring Boot Application Properties files
|
||||
*
|
||||
* @author Alex Boyko
|
||||
*
|
||||
* Deprecated: This should not be used. In fact it isn't used anymore, except for in the tests.
|
||||
* But these tests should be changed to test the 'merged' {@link BootPropertiesLanguageServer}
|
||||
* instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class ApplicationPropertiesLanguageServer extends SimpleLanguageServer {
|
||||
|
||||
private SpringPropertyIndexProvider indexProvider;
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
package org.springframework.ide.vscode.boot.yaml;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionOptions;
|
||||
import org.eclipse.lsp4j.ServerCapabilities;
|
||||
import org.eclipse.lsp4j.TextDocumentSyncKind;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.SpringPropertyIndexProvider;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.completions.PropertyCompletionFactory;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.completions.RelaxedNameConfig;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtilProvider;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap;
|
||||
import org.springframework.ide.vscode.boot.BootPropertiesLanguageServer;
|
||||
import org.springframework.ide.vscode.boot.yaml.completions.ApplicationYamlAssistContext;
|
||||
import org.springframework.ide.vscode.boot.yaml.completions.ApplicationYamlStructureProvider;
|
||||
import org.springframework.ide.vscode.boot.yaml.reconcile.ApplicationYamlReconcileEngine;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter;
|
||||
import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfoProvider;
|
||||
import org.springframework.ide.vscode.commons.languageserver.hover.VscodeHoverEngineAdapter;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.IDocument;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.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.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;
|
||||
|
||||
/**
|
||||
* Deprecated: This should not be used. In fact it isn't used anymore, except for in the tests.
|
||||
* But these tests should be changed to test the 'merged' {@link BootPropertiesLanguageServer}
|
||||
* instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class ApplicationYamlLanguageServer extends SimpleLanguageServer {
|
||||
|
||||
private Yaml yaml = new Yaml();
|
||||
private YamlASTProvider parser = new YamlParser(yaml);
|
||||
private SpringPropertyIndexProvider indexProvider;
|
||||
private TypeUtilProvider typeUtilProvider;
|
||||
private VscodeCompletionEngineAdapter completionEngine;
|
||||
private VscodeHoverEngineAdapter hoverEngine;
|
||||
|
||||
|
||||
public ApplicationYamlLanguageServer(SpringPropertyIndexProvider indexProvider, TypeUtilProvider typeUtilProvider, JavaProjectFinder javaProjectFinder) {
|
||||
this.indexProvider = indexProvider;
|
||||
this.typeUtilProvider = typeUtilProvider;
|
||||
SimpleTextDocumentService documents = getTextDocumentService();
|
||||
// SimpleWorkspaceService workspace = getWorkspaceService();
|
||||
IReconcileEngine reconcileEngine = getReconcileEngine();
|
||||
documents.onDidChangeContent(params -> {
|
||||
TextDocument doc = params.getDocument();
|
||||
validateWith(doc, reconcileEngine);
|
||||
});
|
||||
|
||||
// workspace.onDidChangeConfiguraton(settings -> {
|
||||
// System.out.println("Config changed: "+params);
|
||||
// Integer val = settings.getInt("languageServerExample", "maxNumberOfProblems");
|
||||
// if (val!=null) {
|
||||
// maxProblems = ((Number) val).intValue();
|
||||
// for (TextDocument doc : documents.getAll()) {
|
||||
// validateDocument(documents, doc);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
YamlStructureProvider structureProvider = ApplicationYamlStructureProvider.INSTANCE;
|
||||
RelaxedNameConfig relaxedNameConfig = RelaxedNameConfig.COMPLETION_DEFAULTS;
|
||||
|
||||
final PropertyCompletionFactory completionFactory = new PropertyCompletionFactory(javaProjectFinder);
|
||||
YamlAssistContextProvider contextProvider = new YamlAssistContextProvider() {
|
||||
@Override
|
||||
public YamlAssistContext getGlobalAssistContext(YamlDocument ydoc) {
|
||||
IDocument doc = ydoc.getDocument();
|
||||
FuzzyMap<PropertyInfo> index = indexProvider.getIndex(doc);
|
||||
return ApplicationYamlAssistContext.global(index, completionFactory, typeUtilProvider.getTypeUtil(doc), relaxedNameConfig);
|
||||
}
|
||||
};
|
||||
YamlCompletionEngine yamlCompletionEngine = new YamlCompletionEngine(structureProvider, contextProvider);
|
||||
|
||||
completionEngine = new VscodeCompletionEngineAdapter(this, yamlCompletionEngine);
|
||||
documents.onCompletion(completionEngine::getCompletions);
|
||||
documents.onCompletionResolve(completionEngine::resolveCompletion);
|
||||
|
||||
HoverInfoProvider infoProvider = new YamlHoverInfoProvider(parser, structureProvider, contextProvider);
|
||||
hoverEngine = new VscodeHoverEngineAdapter(this, infoProvider);
|
||||
documents.onHover(hoverEngine::getHover);
|
||||
}
|
||||
|
||||
public void setMaxCompletionsNumber(int number) {
|
||||
completionEngine.setMaxCompletionsNumber(number);
|
||||
}
|
||||
|
||||
protected IReconcileEngine getReconcileEngine() {
|
||||
return new ApplicationYamlReconcileEngine(parser, indexProvider, typeUtilProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerCapabilities getServerCapabilities() {
|
||||
ServerCapabilities c = new ServerCapabilities();
|
||||
|
||||
c.setTextDocumentSync(TextDocumentSyncKind.Full);
|
||||
|
||||
CompletionOptions completionProvider = new CompletionOptions();
|
||||
completionProvider.setResolveProvider(false);
|
||||
c.setCompletionProvider(completionProvider);
|
||||
c.setHoverProvider(true);
|
||||
|
||||
return c;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,529 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015, 2016 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.yaml.completions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.ide.vscode.application.properties.metadata.IndexNavigator;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.completions.PropertyCompletionFactory;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.completions.RelaxedNameConfig;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.hints.HintProvider;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.hints.StsValueHint;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.hints.ValueHintHoverInfo;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.hover.PropertyRenderableProvider;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.Type;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeParser;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.BeanPropertyNameMode;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.EnumCaseMode;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypedProperty;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap.Match;
|
||||
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;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
|
||||
import org.springframework.ide.vscode.commons.util.CollectionUtil;
|
||||
import org.springframework.ide.vscode.commons.util.FuzzyMatcher;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
import org.springframework.ide.vscode.commons.yaml.completion.AbstractYamlAssistContext;
|
||||
import org.springframework.ide.vscode.commons.yaml.completion.TopLevelAssistContext;
|
||||
import org.springframework.ide.vscode.commons.yaml.completion.YamlAssistContext;
|
||||
import org.springframework.ide.vscode.commons.yaml.completion.YamlPathEdits;
|
||||
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.path.YamlPathSegment.YamlPathSegmentType;
|
||||
import org.springframework.ide.vscode.commons.yaml.structure.YamlDocument;
|
||||
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SChildBearingNode;
|
||||
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SKeyNode;
|
||||
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SNode;
|
||||
import org.springframework.ide.vscode.commons.yaml.util.YamlIndentUtil;
|
||||
import org.springframework.ide.vscode.commons.yaml.util.YamlUtil;
|
||||
|
||||
/**
|
||||
* Represents a context insied a "application.yml" file relative to which we can provide
|
||||
* content assistance.
|
||||
*/
|
||||
public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistContext {
|
||||
|
||||
protected final RelaxedNameConfig conf;
|
||||
|
||||
// This may prove useful later but we don't need it for now
|
||||
// /**
|
||||
// * AssistContextKind is an classification of the different kinds of
|
||||
// * syntactic context that CA can be invoked from.
|
||||
// */
|
||||
// public static enum Kind {
|
||||
// SKEY_KEY, /* CA called from a SKeyNode and node.isInKey(cursor)==true */
|
||||
// SKEY_VALUE, /* CA called from a SKeyNode and node.isInKey(cursor)==false */
|
||||
// SRAW /* CA called from a SRawNode */
|
||||
// }
|
||||
// protected final Kind contextKind;
|
||||
|
||||
public final TypeUtil typeUtil;
|
||||
|
||||
public ApplicationYamlAssistContext(int documentSelector, YamlPath contextPath, TypeUtil typeUtil, RelaxedNameConfig conf) {
|
||||
super(documentSelector, contextPath);
|
||||
this.typeUtil = typeUtil;
|
||||
this.conf = conf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the text that should be appended at the end of a completion
|
||||
* proposal depending on what type of value is expected.
|
||||
*/
|
||||
protected String appendTextFor(Type type) {
|
||||
//Note that proper indentation after each \n" is added automatically
|
||||
//so the strings created here do not need to contain indentation spaces
|
||||
if (TypeUtil.isMap(type)) {
|
||||
//ready to enter nested map key on next line
|
||||
return "\n"+YamlIndentUtil.INDENT_STR;
|
||||
} if (TypeUtil.isSequencable(type)) {
|
||||
//ready to enter sequence element on next line
|
||||
return "\n- ";
|
||||
} else if (typeUtil.isAtomic(type)) {
|
||||
//ready to enter whatever on the same line
|
||||
return " ";
|
||||
} else {
|
||||
//Assume its some kind of pojo bean
|
||||
return "\n"+YamlIndentUtil.INDENT_STR;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the type expected at this context, may return null if unknown.
|
||||
*/
|
||||
protected abstract Type getType();
|
||||
|
||||
public static ApplicationYamlAssistContext subdocument(int documentSelector, FuzzyMap<PropertyInfo> index, PropertyCompletionFactory completionFactory, TypeUtil typeUtil, RelaxedNameConfig conf) {
|
||||
return new IndexContext(documentSelector, YamlPath.EMPTY, IndexNavigator.with(index), completionFactory, typeUtil, conf);
|
||||
}
|
||||
|
||||
public static YamlAssistContext forPath(YamlPath contextPath, FuzzyMap<PropertyInfo> index, PropertyCompletionFactory completionFactory, TypeUtil typeUtil, RelaxedNameConfig conf) {
|
||||
try {
|
||||
YamlPathSegment documentSelector = contextPath.getSegment(0);
|
||||
if (documentSelector!=null) {
|
||||
contextPath = contextPath.dropFirst(1);
|
||||
YamlAssistContext context = ApplicationYamlAssistContext.subdocument(documentSelector.toIndex(), index, completionFactory, typeUtil, conf);
|
||||
for (YamlPathSegment s : contextPath.getSegments()) {
|
||||
if (context==null) return null;
|
||||
context = context.traverse(s);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
abstract public YamlAssistContext traverse(YamlPathSegment s) throws Exception;
|
||||
|
||||
private static class TypeContext extends ApplicationYamlAssistContext {
|
||||
|
||||
private PropertyCompletionFactory completionFactory;
|
||||
private Type type;
|
||||
private ApplicationYamlAssistContext parent;
|
||||
private HintProvider hints;
|
||||
|
||||
public TypeContext(ApplicationYamlAssistContext parent, YamlPath contextPath, Type type,
|
||||
PropertyCompletionFactory completionFactory, TypeUtil typeUtil, RelaxedNameConfig conf, HintProvider hints) {
|
||||
super(parent.documentSelector, contextPath, typeUtil, conf);
|
||||
this.parent = parent;
|
||||
this.completionFactory = completionFactory;
|
||||
this.type = type;
|
||||
this.hints = hints;
|
||||
}
|
||||
|
||||
private HintProvider getHintProvider() {
|
||||
return hints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ICompletionProposal> getCompletions(YamlDocument doc, SNode node, int offset) throws Exception {
|
||||
String query = getPrefix(doc, node, offset);
|
||||
EnumCaseMode enumCaseMode = enumCaseMode(query);
|
||||
BeanPropertyNameMode beanMode = conf.getBeanMode();
|
||||
List<ICompletionProposal> valueCompletions = getValueCompletions(doc, offset, query, enumCaseMode);
|
||||
if (!valueCompletions.isEmpty()) {
|
||||
return valueCompletions;
|
||||
}
|
||||
return getKeyCompletions(doc, offset, query, enumCaseMode, beanMode);
|
||||
}
|
||||
|
||||
private EnumCaseMode enumCaseMode(String query) {
|
||||
if (query.isEmpty()) {
|
||||
return conf.getEnumMode();
|
||||
} else {
|
||||
return EnumCaseMode.ALIASED; // will match candidates from both lower and original based on what user typed
|
||||
}
|
||||
}
|
||||
|
||||
public List<ICompletionProposal> getKeyCompletions(YamlDocument doc, int offset, String query,
|
||||
EnumCaseMode enumCaseMode, BeanPropertyNameMode beanMode) throws Exception {
|
||||
int queryOffset = offset - query.length();
|
||||
List<TypedProperty> properties = getProperties(query, enumCaseMode, beanMode);
|
||||
if (CollectionUtil.hasElements(properties)) {
|
||||
ArrayList<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>(properties.size());
|
||||
SNode contextNode = getContextNode(doc);
|
||||
Set<String> definedProps = getDefinedProperties(contextNode);
|
||||
for (TypedProperty p : properties) {
|
||||
String name = p.getName();
|
||||
double score = FuzzyMatcher.matchScore(query, name);
|
||||
if (score!=0) {
|
||||
YamlPath relativePath = YamlPath.fromSimpleProperty(name);
|
||||
YamlPathEdits edits = new YamlPathEdits(doc);
|
||||
if (!definedProps.contains(name)) {
|
||||
//property not yet defined
|
||||
Type type = p.getType();
|
||||
edits.delete(queryOffset, query);
|
||||
edits.createPathInPlace(contextNode, relativePath, queryOffset, appendTextFor(type));
|
||||
proposals.add(completionFactory.beanProperty(doc.getDocument(),
|
||||
contextPath.toPropString(), getType(),
|
||||
query, p, score, edits, typeUtil)
|
||||
);
|
||||
} else {
|
||||
//property already defined
|
||||
// instead of filtering, navigate to the place where its defined.
|
||||
deleteQueryAndLine(doc, query, queryOffset, edits);
|
||||
//Cast to SChildBearingNode cannot fail because otherwise definedProps would be the empty set.
|
||||
edits.createPath((SChildBearingNode) contextNode, relativePath, "");
|
||||
proposals.add(
|
||||
completionFactory.beanProperty(doc.getDocument(),
|
||||
contextPath.toPropString(), getType(),
|
||||
query, p, score, edits, typeUtil)
|
||||
.deemphasize() //deemphasize because it already exists
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return proposals;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
protected List<TypedProperty> getProperties(String query, EnumCaseMode enumCaseMode, BeanPropertyNameMode beanMode) {
|
||||
ArrayList<TypedProperty> props = new ArrayList<>();
|
||||
List<TypedProperty> fromType = typeUtil.getProperties(type, enumCaseMode, beanMode);
|
||||
if (CollectionUtil.hasElements(fromType)) {
|
||||
props.addAll(fromType);
|
||||
}
|
||||
HintProvider hints = getHintProvider();
|
||||
if (hints!=null) {
|
||||
List<TypedProperty> fromHints = hints.getPropertyHints(query);
|
||||
if (CollectionUtil.hasElements(fromHints)) {
|
||||
props.addAll(fromHints);
|
||||
}
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
private Set<String> getDefinedProperties(SNode contextNode) {
|
||||
try {
|
||||
if (contextNode instanceof SChildBearingNode) {
|
||||
List<SNode> children = ((SChildBearingNode)contextNode).getChildren();
|
||||
if (CollectionUtil.hasElements(children)) {
|
||||
Set<String> keys = new HashSet<String>(children.size());
|
||||
for (SNode c : children) {
|
||||
if (c instanceof SKeyNode) {
|
||||
keys.add(((SKeyNode) c).getKey());
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
private List<ICompletionProposal> getValueCompletions(YamlDocument doc, int offset, String query, EnumCaseMode enumCaseMode) {
|
||||
Collection<StsValueHint> hints = getHintValues(query, doc, offset, enumCaseMode);
|
||||
if (hints!=null) {
|
||||
ArrayList<ICompletionProposal> completions = new ArrayList<ICompletionProposal>();
|
||||
for (StsValueHint hint : hints) {
|
||||
String value = hint.getValue();
|
||||
double score = FuzzyMatcher.matchScore(query, value);
|
||||
if (score!=0 && !value.equals(query)) {
|
||||
DocumentEdits edits = new DocumentEdits(doc.getDocument());
|
||||
int valueStart = offset-query.length();
|
||||
edits.delete(valueStart, offset);
|
||||
if (doc.getChar(valueStart-1)==':') {
|
||||
edits.insert(offset, " ");
|
||||
}
|
||||
edits.insert(offset, YamlUtil.stringEscape(value));
|
||||
completions.add(completionFactory.valueProposal(value, query, type, score, edits, new ValueHintHoverInfo(hint)));
|
||||
}
|
||||
}
|
||||
return completions;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable getValueHoverInfo(YamlDocument doc, DocumentRegion valueRegion) {
|
||||
String value = valueRegion.toString();
|
||||
|
||||
if (TypeUtil.isClass(type)) {
|
||||
//Special case. We want hovers/hyperlinks even if the class is not a valid hint (as long as it is a class)
|
||||
StsValueHint hint = StsValueHint.className(value.toString(), typeUtil);
|
||||
if (hint!=null) {
|
||||
return hint.getDescription();
|
||||
}
|
||||
}
|
||||
|
||||
Collection<StsValueHint> hints = getHintValues(value, doc, valueRegion.getEnd(), EnumCaseMode.ALIASED);
|
||||
//The hints where found by fuzzy match so they may not actually match exactly!
|
||||
for (StsValueHint h : hints) {
|
||||
if (value.equals(h.getValue())) {
|
||||
return h.getDescription();
|
||||
}
|
||||
}
|
||||
return getHoverInfo();
|
||||
}
|
||||
|
||||
protected Collection<StsValueHint> getHintValues(
|
||||
String query,
|
||||
YamlDocument doc, int offset,
|
||||
EnumCaseMode enumCaseMode
|
||||
) {
|
||||
Collection<StsValueHint> allHints = new ArrayList<>();
|
||||
{
|
||||
Collection<StsValueHint> hints = typeUtil.getHintValues(type, query, enumCaseMode);
|
||||
if (CollectionUtil.hasElements(hints)) {
|
||||
allHints.addAll(hints);
|
||||
}
|
||||
}
|
||||
{
|
||||
HintProvider hintProvider = getHintProvider();
|
||||
if (hintProvider!=null) {
|
||||
allHints.addAll(hintProvider.getValueHints(query));
|
||||
}
|
||||
}
|
||||
return allHints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public YamlAssistContext traverse(YamlPathSegment s) {
|
||||
if (s.getType()==YamlPathSegmentType.VAL_AT_KEY) {
|
||||
if (TypeUtil.isSequencable(type) || TypeUtil.isMap(type)) {
|
||||
return contextWith(s, TypeUtil.getDomainType(type));
|
||||
}
|
||||
String key = s.toPropString();
|
||||
Map<String, TypedProperty> subproperties = typeUtil.getPropertiesMap(type, EnumCaseMode.ALIASED, BeanPropertyNameMode.ALIASED);
|
||||
if (subproperties!=null) {
|
||||
return contextWith(s, TypedProperty.typeOf(subproperties.get(key)));
|
||||
}
|
||||
} else if (s.getType()==YamlPathSegmentType.VAL_AT_INDEX) {
|
||||
if (TypeUtil.isSequencable(type)) {
|
||||
return contextWith(s, TypeUtil.getDomainType(type));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TypeContext("+contextPath.toPropString()+"::"+type+")";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Renderable getHoverInfo() {
|
||||
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.getHoverInfo();
|
||||
} else {
|
||||
return new JavaTypeNavigationHoverInfo(contextPath.toPropString(), contextPath.getBeanPropertyName(), parent.getType(), getType(), typeUtil).getRenderable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Renderable getHoverInfo(YamlPathSegment lastSegment) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class IndexContext extends ApplicationYamlAssistContext {
|
||||
|
||||
private IndexNavigator indexNav;
|
||||
PropertyCompletionFactory completionFactory;
|
||||
|
||||
public IndexContext(int documentSelector, YamlPath contextPath, IndexNavigator indexNav,
|
||||
PropertyCompletionFactory completionFactory, TypeUtil typeUtil, RelaxedNameConfig conf) {
|
||||
super(documentSelector, contextPath, typeUtil, conf);
|
||||
this.indexNav = indexNav;
|
||||
this.completionFactory = completionFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ICompletionProposal> getCompletions(YamlDocument doc, SNode node, int offset) throws Exception {
|
||||
String query = getPrefix(doc, node, offset);
|
||||
Collection<Match<PropertyInfo>> matchingProps = indexNav.findMatching(query);
|
||||
if (!matchingProps.isEmpty()) {
|
||||
ArrayList<ICompletionProposal> completions = new ArrayList<ICompletionProposal>();
|
||||
for (Match<PropertyInfo> match : matchingProps) {
|
||||
DocumentEdits edits = createEdits(doc, offset, query, match);
|
||||
ScoreableProposal completion = completionFactory.property(
|
||||
doc.getDocument(), edits, match, typeUtil
|
||||
);
|
||||
if (getContextRoot(doc).exists(YamlPath.fromProperty(match.data.getId()))) {
|
||||
completion.deemphasize();
|
||||
}
|
||||
completions.add(completion);
|
||||
}
|
||||
return completions;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
protected DocumentEdits createEdits(final YamlDocument file,
|
||||
final int offset, final String query, final Match<PropertyInfo> match)
|
||||
throws Exception {
|
||||
//Edits created lazyly as they are somwehat expensive to compute and mostly
|
||||
// we need only the edits for the one proposal that user picks.
|
||||
return LazyProposalApplier.from(() -> {
|
||||
YamlPathEdits edits = new YamlPathEdits(file);
|
||||
|
||||
int queryOffset = offset-query.length();
|
||||
edits.delete(queryOffset, query);
|
||||
|
||||
YamlPath propertyPath = YamlPath.fromProperty(match.data.getId());
|
||||
YamlPath relativePath = propertyPath.dropFirst(contextPath.size());
|
||||
YamlPathSegment nextSegment = relativePath.getSegment(0);
|
||||
SNode contextNode = getContextNode(file);
|
||||
//To determine if this completion is 'in place' or needs to be inserted
|
||||
// elsewhere in the tree, we check whether a node already exists in our
|
||||
// context. If it doesn't we can create it as any child of the context
|
||||
// so that includes, right at place the user is typing now.
|
||||
SNode existingNode = contextNode.traverse(nextSegment);
|
||||
String appendText = appendTextFor(TypeParser.parse(match.data.getType()));
|
||||
if (existingNode==null) {
|
||||
edits.createPathInPlace(contextNode, relativePath, queryOffset, appendText);
|
||||
} else {
|
||||
String wholeLine = file.getLineTextAtOffset(queryOffset);
|
||||
if (wholeLine.trim().equals(query.trim())) {
|
||||
edits.deleteLineBackwardAtOffset(queryOffset);
|
||||
}
|
||||
edits.createPath(getContextRoot(file), YamlPath.fromProperty(match.data.getId()), appendText);
|
||||
}
|
||||
return edits;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractYamlAssistContext traverse(YamlPathSegment s) {
|
||||
if (s.getType()==YamlPathSegmentType.VAL_AT_KEY) {
|
||||
String key = s.toPropString();
|
||||
IndexNavigator subIndex = indexNav.selectSubProperty(key);
|
||||
if (subIndex.isEmpty()) {
|
||||
//Nothing found for actual key... maybe its a 'camelCased' alias of real key?
|
||||
String keyAlias = StringUtil.camelCaseToHyphens(key);
|
||||
if (!keyAlias.equals(key)) { // no point checking alias is the same (likely key was not camelCased)
|
||||
IndexNavigator aliasedSubIndex = indexNav.selectSubProperty(keyAlias);
|
||||
if (!aliasedSubIndex.isEmpty()) {
|
||||
subIndex = aliasedSubIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (subIndex.getExtensionCandidate()!=null) {
|
||||
return new IndexContext(documentSelector, contextPath.append(s), subIndex, completionFactory, typeUtil, conf);
|
||||
} else if (subIndex.getExactMatch()!=null) {
|
||||
IndexContext asIndexContext = new IndexContext(documentSelector, contextPath.append(s), subIndex, completionFactory, typeUtil, conf);
|
||||
PropertyInfo prop = subIndex.getExactMatch();
|
||||
return new TypeContext(asIndexContext, contextPath.append(s), TypeParser.parse(prop.getType()), completionFactory, typeUtil, conf, prop.getHints(typeUtil, true));
|
||||
}
|
||||
}
|
||||
//Unsuported navigation => no context for assist
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "YamlAssistIndexContext("+indexNav+")";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Type getType() {
|
||||
PropertyInfo match = indexNav.getExactMatch();
|
||||
if (match!=null) {
|
||||
return TypeParser.parse(match.getType());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable getHoverInfo() {
|
||||
PropertyInfo prop = indexNav.getExactMatch();
|
||||
if (prop!=null) {
|
||||
return new PropertyRenderableProvider(typeUtil.getJavaProject(), prop).getRenderable();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable getHoverInfo(YamlPathSegment lastSegment) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable getValueHoverInfo(YamlDocument doc, DocumentRegion documentRegion) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Renderable getHoverInfo(YamlPathSegment s) {
|
||||
//ApplicationYamlAssistContext implements getHoverInfo directly. so this is not needed.
|
||||
return null;
|
||||
}
|
||||
|
||||
public static YamlAssistContext global(final FuzzyMap<PropertyInfo> index, final PropertyCompletionFactory completionFactory, final TypeUtil typeUtil, final RelaxedNameConfig conf) {
|
||||
return new TopLevelAssistContext() {
|
||||
@Override
|
||||
protected YamlAssistContext getDocumentContext(int documentSelector) {
|
||||
return subdocument(documentSelector, index, completionFactory, typeUtil, conf);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015, 2016 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.yaml.completions;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
import org.springframework.ide.vscode.commons.yaml.path.KeyAliases;
|
||||
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureProvider;
|
||||
|
||||
public class ApplicationYamlStructureProvider {
|
||||
|
||||
private static final KeyAliases KEY_ALIASES = new KeyAliases() {
|
||||
@Override
|
||||
public Iterable<String> getKeyAliases(String base) {
|
||||
String camelCased = StringUtil.hyphensToCamelCase(base, false);
|
||||
if (!camelCased.equals(base)) {
|
||||
return Collections.singletonList(camelCased);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
};
|
||||
|
||||
public static final YamlStructureProvider INSTANCE = YamlStructureProvider.withAliases(KEY_ALIASES);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015, 2016 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.yaml.completions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.springframework.boot.configurationmetadata.Deprecation;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.hover.AbstractPropertyRenderableProvider;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.Type;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.BeanPropertyNameMode;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.EnumCaseMode;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypedProperty;
|
||||
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.javadoc.IJavadoc;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
|
||||
/**
|
||||
* Example used as reference for explaingin the meaning of the instance variables:
|
||||
*
|
||||
* foo.bar[0].children.wavelen
|
||||
*/
|
||||
public class JavaTypeNavigationHoverInfo extends AbstractPropertyRenderableProvider {
|
||||
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(JavaTypeNavigationHoverInfo.class.getName());
|
||||
|
||||
/**
|
||||
* Property expression that represents the full path to the point being hovered over.
|
||||
* E.g. foo.bar[0].data.wavelen"
|
||||
*/
|
||||
private final String id;
|
||||
|
||||
/**
|
||||
* Last sgment of the path as a property name.
|
||||
* This is only set if the naviation is accessing a property name (so, for example, will be null for navigation into
|
||||
* indexed element in a sequence/list)
|
||||
* <p>
|
||||
* Example: "wavelen"
|
||||
*/
|
||||
private final String propName;
|
||||
|
||||
/**
|
||||
* The type from which we are navigating.
|
||||
* E.g. the type of "foo.bar[0].data"
|
||||
*/
|
||||
private Type parentType;
|
||||
|
||||
/**
|
||||
* The type at which we arrive.
|
||||
* E.g the type of "foo.bar[0].data.wavelen"
|
||||
*/
|
||||
private Type type;
|
||||
|
||||
private TypeUtil typeUtil;
|
||||
|
||||
private Deprecation deprecation;
|
||||
|
||||
public JavaTypeNavigationHoverInfo(String id, String propName, Type fromType, Type toType, TypeUtil typeUtil) {
|
||||
this.id = id;
|
||||
this.propName = propName;
|
||||
this.parentType = fromType;
|
||||
this.type = toType;
|
||||
this.typeUtil = typeUtil;
|
||||
//Note: If you are considerind caching the result of this method... don't.
|
||||
//The rendered html itself is cached by the superclass, which means this method only gets called once.
|
||||
Map<String, TypedProperty> props = typeUtil.getPropertiesMap(parentType, EnumCaseMode.ALIASED, BeanPropertyNameMode.ALIASED);
|
||||
if (props!=null) {
|
||||
TypedProperty prop = props.get(propName);
|
||||
if (prop!=null) {
|
||||
this.deprecation = prop.getDeprecation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @Override
|
||||
// protected String renderAsHtml() {
|
||||
// JavaTypeLinks jtLinks = new JavaTypeLinks(this);
|
||||
// HtmlBuffer html = new HtmlBuffer();
|
||||
//
|
||||
// html.raw("<b>");
|
||||
// html.text(id);
|
||||
// html.raw("</b>");
|
||||
// html.raw("<br>");
|
||||
//
|
||||
// if (type!=null) {
|
||||
// jtLinks.javaTypeLink(html, typeUtil, type);
|
||||
// } else {
|
||||
// jtLinks.javaTypeLink(html, typeUtil.getJavaProject(), Object.class.toString());
|
||||
// }
|
||||
//
|
||||
// if (isDeprecated()) {
|
||||
// html.raw("<br><br>");
|
||||
// html.bold("Deprecated!");
|
||||
// }
|
||||
//
|
||||
// // String deflt = formatDefaultValue(data.getDefaultValue());
|
||||
// // if (deflt!=null) {
|
||||
// // html.raw("<br><br>");
|
||||
// // html.text("Default: ");
|
||||
// // html.raw("<i>");
|
||||
// // html.text(deflt);
|
||||
// // html.raw("</i>");
|
||||
// // }
|
||||
//
|
||||
// String description = getDescription();
|
||||
// if (description!=null) {
|
||||
// html.raw("<br><br>");
|
||||
// html.raw(description);
|
||||
// }
|
||||
//
|
||||
// return html.toString();
|
||||
// }
|
||||
|
||||
@Override
|
||||
protected boolean isDeprecated() {
|
||||
return deprecation!=null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Renderable getDescription() {
|
||||
try {
|
||||
List<IJavaElement> jes = getAllJavaElements();
|
||||
if (jes!=null) {
|
||||
for (IJavaElement je : jes) {
|
||||
if (je instanceof IMember) {
|
||||
IJavadoc javadoc = je.getJavaDoc();
|
||||
if (javadoc != null) {
|
||||
return javadoc.getRenderable();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.log(Level.SEVERE, e.getMessage(), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<IJavaElement> getJavaElements() {
|
||||
if (propName!=null) {
|
||||
if (TypeUtil.isMap(parentType)) {
|
||||
Type enumType = typeUtil.getKeyType(parentType);
|
||||
if (typeUtil.isEnum(enumType)) {
|
||||
IField f = typeUtil.getEnumConstant(enumType, propName);
|
||||
if (f!=null) {
|
||||
return ImmutableList.of(f);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
IJavaElement je;
|
||||
Type beanType = parentType;
|
||||
je = typeUtil.getSetter(beanType, propName).get();
|
||||
if (je!=null) {
|
||||
return Collections.singletonList(je);
|
||||
}
|
||||
je = typeUtil.getGetter(beanType, propName);
|
||||
if (je!=null) {
|
||||
return Collections.singletonList(je);
|
||||
}
|
||||
je = typeUtil.getField(beanType, propName);
|
||||
if (je!=null) {
|
||||
return Collections.singletonList(je);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private List<IJavaElement> getAllJavaElements() {
|
||||
if (propName!=null) {
|
||||
Type beanType = parentType;
|
||||
if (TypeUtil.isMap(beanType)) {
|
||||
Type keyType = typeUtil.getKeyType(beanType);
|
||||
if (keyType!=null && typeUtil.isEnum(keyType)) {
|
||||
IField field = typeUtil.getEnumConstant(keyType, propName);
|
||||
if (field!=null) {
|
||||
return ImmutableList.of(field);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ArrayList<IJavaElement> elements = new ArrayList<IJavaElement>(3);
|
||||
maybeAdd(elements, typeUtil.getField(beanType, propName));
|
||||
maybeAdd(elements, typeUtil.getSetter(beanType, propName).get());
|
||||
maybeAdd(elements, typeUtil.getGetter(beanType, propName));
|
||||
if (!elements.isEmpty()) {
|
||||
return elements;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
private void maybeAdd(ArrayList<IJavaElement> elements, IJavaElement e) {
|
||||
if (e!=null) {
|
||||
elements.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getDefaultValue() {
|
||||
//Not supported
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IJavaProject getJavaProject() {
|
||||
return typeUtil.getJavaProject();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getType() {
|
||||
return typeUtil.niceTypeName(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDeprecationReason() {
|
||||
if (deprecation!=null) {
|
||||
return deprecation.getReason();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDeprecationReplacement() {
|
||||
if (deprecation!=null) {
|
||||
return deprecation.getReplacement();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 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.yaml.quickfix;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
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.quickfix.ProblemFixer;
|
||||
|
||||
public class ReplaceDeprecatedYamlQuickfix implements ICompletionProposal {
|
||||
|
||||
public static ProblemFixer FIXER = (context, problem, proposals) -> {
|
||||
throw new UnsupportedOperationException("Not yet implemented");
|
||||
// PropertyInfo metadata = problem.getMetadata();
|
||||
// if (metadata!=null) {
|
||||
// String replacement = metadata.getDeprecationReplacement();
|
||||
// if (replacement!=null) {
|
||||
// //No need to check problem type... we only attach this fixer to problems of applicable type.
|
||||
// proposals.add(new ReplaceDeprecatedYamlQuickfix(context, problem));
|
||||
// }
|
||||
// }
|
||||
};
|
||||
|
||||
@Override
|
||||
public ICompletionProposal deemphasize() {
|
||||
throw new UnsupportedOperationException("Not yet implemented");
|
||||
}
|
||||
@Override
|
||||
public String getLabel() {
|
||||
throw new UnsupportedOperationException("Not yet implemented");
|
||||
}
|
||||
@Override
|
||||
public CompletionItemKind getKind() {
|
||||
throw new UnsupportedOperationException("Not yet implemented");
|
||||
}
|
||||
@Override
|
||||
public DocumentEdits getTextEdit() {
|
||||
throw new UnsupportedOperationException("Not yet implemented");
|
||||
}
|
||||
|
||||
// private final QuickfixContext context;
|
||||
// private final SpringPropertyProblem problem;
|
||||
//
|
||||
// private LazyProposalApplier applier = new LazyProposalApplier() {
|
||||
// protected ProposalApplier create() throws Exception {
|
||||
// String newName = problem.getMetadata().getDeprecationReplacement();
|
||||
// String oldName = problem.getPropertyName();
|
||||
// YamlPath newPath = YamlPath.fromProperty(newName);
|
||||
// YamlPath oldPath = YamlPath.fromProperty(oldName);
|
||||
// YamlPath prefix = newPath.commonPrefix(oldPath);
|
||||
// if (prefix.size()==newPath.size()-1 && newPath.size()==oldPath.size()) {
|
||||
// //only the last segment has changed. We can do a simple 'in-place' replace
|
||||
// // of just the change segment.
|
||||
// DocumentEdits edits = new DocumentEdits(context.getDocument());
|
||||
// edits.replace(problem.getOffset(), problem.getEnd(), newPath.getLastSegment().toPropString());
|
||||
// return edits;
|
||||
// }
|
||||
// YamlDocument doc = new YamlDocument(context.getDocument(), YamlStructureProvider.DEFAULT);
|
||||
// SNode problemNode = doc.getStructure().find(problem.getOffset());
|
||||
// if (problemNode.getNodeType()==SNodeType.KEY) {
|
||||
// SKeyNode problemKey = (SKeyNode) problemNode;
|
||||
// if (problemKey.isInKey(problem.getOffset())) {
|
||||
// YamlPathEdits edits = new YamlPathEdits(doc);
|
||||
//// print(doc, edits);
|
||||
// String valueText = problemKey.getValueWithRelativeIndent();
|
||||
// edits.deleteNode(problemKey);
|
||||
// int maxParentDeletions = oldPath.size() - prefix.size() - 1; // don't delete bits of the common prefix!
|
||||
// SChildBearingNode parent = problemNode.getParent();
|
||||
// while (maxParentDeletions>0 && parent!=null && parent.getChildren().size()==1) {
|
||||
// edits.deleteNode(parent);
|
||||
// parent = parent.getParent();
|
||||
// maxParentDeletions--;
|
||||
// }
|
||||
//// print(doc, edits);
|
||||
// SDocNode docRoot = problemNode.getDocNode(); //edits should stay within the same 'document' for yaml file that has multiple documents inside of it.
|
||||
// edits.createPath(docRoot, YamlPath.fromProperty(newName), valueText);
|
||||
//// print(doc, edits);
|
||||
// return edits;
|
||||
// }
|
||||
// }
|
||||
// //Not sure what to do... case not covered... so do nothing but tell the user.
|
||||
// context.getUI().error("Yaml file too complex",
|
||||
// "Sorry, but the yaml file is too complex for this quickfix. " +
|
||||
// "Please make the change manually."
|
||||
// );
|
||||
// return ProposalApplier.NULL;
|
||||
// }
|
||||
//
|
||||
//// private void print(YamlDocument doc, YamlPathEdits edits) throws Exception {
|
||||
//// Document workingCopy = new Document(doc.getDocument().get());
|
||||
//// edits.apply(workingCopy);
|
||||
//// System.out.println("==============");
|
||||
//// System.out.println(workingCopy.get());
|
||||
//// System.out.println("==============");
|
||||
//// }
|
||||
// };
|
||||
//
|
||||
// public ReplaceDeprecatedYamlQuickfix(QuickfixContext context, SpringPropertyProblem problem) {
|
||||
// this.context = context;
|
||||
// this.problem = problem;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void apply(IDocument doc) {
|
||||
// try {
|
||||
// applier.apply(doc);
|
||||
// } catch (Exception e) {
|
||||
// Log.log(e);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private String getReplacementProperty() {
|
||||
// return problem.getMetadata().getDeprecationReplacement();
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Point getSelection(IDocument doc) {
|
||||
// try {
|
||||
// return applier.getSelection(doc);
|
||||
// } catch (Exception e) {
|
||||
// Log.log(e);
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public String getAdditionalProposalInfo() {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public String getDisplayString() {
|
||||
// return "Change to '"+getReplacementProperty()+"'";
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Image getImage() {
|
||||
// return JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public IContextInformation getContextInformation() {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
package org.springframework.ide.vscode.boot.yaml.reconcile;
|
||||
|
||||
import static org.springframework.ide.vscode.boot.yaml.reconcile.ApplicationYamlProblemType.YAML_DEPRECATED;
|
||||
import static org.springframework.ide.vscode.boot.yaml.reconcile.ApplicationYamlProblemType.YAML_DUPLICATE_KEY;
|
||||
import static org.springframework.ide.vscode.commons.yaml.ast.NodeUtil.asScalar;
|
||||
import static org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST.getChildren;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.ide.vscode.application.properties.metadata.IndexNavigator;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.Type;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeParser;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.BeanPropertyNameMode;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.EnumCaseMode;
|
||||
import org.springframework.ide.vscode.boot.yaml.quickfix.ReplaceDeprecatedYamlQuickfix;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypedProperty;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
import org.springframework.ide.vscode.commons.util.ValueParser;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.NodeRef;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.NodeRef.Kind;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.NodeRef.TupleValueRef;
|
||||
import org.springframework.ide.vscode.commons.yaml.reconcile.YamlASTReconciler;
|
||||
import org.yaml.snakeyaml.nodes.MappingNode;
|
||||
import org.yaml.snakeyaml.nodes.Node;
|
||||
import org.yaml.snakeyaml.nodes.NodeId;
|
||||
import org.yaml.snakeyaml.nodes.NodeTuple;
|
||||
import org.yaml.snakeyaml.nodes.ScalarNode;
|
||||
import org.yaml.snakeyaml.nodes.SequenceNode;
|
||||
|
||||
/**
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class ApplicationYamlASTReconciler implements YamlASTReconciler {
|
||||
|
||||
private final IProblemCollector problems;
|
||||
private final TypeUtil typeUtil;
|
||||
private final IndexNavigator nav;
|
||||
|
||||
public ApplicationYamlASTReconciler(IProblemCollector problems, IndexNavigator nav, TypeUtil typeUtil) {
|
||||
this.problems = problems;
|
||||
this.typeUtil = typeUtil;
|
||||
this.nav = nav;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconcile(YamlFileAST ast) {
|
||||
reconcile(ast, nav);
|
||||
}
|
||||
|
||||
protected void reconcile(YamlFileAST ast, IndexNavigator nav) {
|
||||
List<Node> nodes = ast.getNodes();
|
||||
if (nodes!=null && !nodes.isEmpty()) {
|
||||
for (Node node : nodes) {
|
||||
reconcile(node, nav);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void reconcile(Node node, IndexNavigator nav) {
|
||||
switch (node.getNodeId()) {
|
||||
case mapping:
|
||||
checkForDuplicateKeys((MappingNode)node);
|
||||
for (NodeTuple entry : ((MappingNode)node).getValue()) {
|
||||
reconcile(entry, nav);
|
||||
}
|
||||
break;
|
||||
case scalar:
|
||||
if (!isIgnoreScalarAssignmentTo(nav.getPrefix())) {
|
||||
expectMapping(node);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
expectMapping(node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkForDuplicateKeys(MappingNode node) {
|
||||
Set<String> duplicateKeys = new HashSet<>();
|
||||
Set<String> seenKeys = new HashSet<>();
|
||||
for (NodeTuple entry : node.getValue()) {
|
||||
String key = asScalar(entry.getKeyNode());
|
||||
if (key!=null) {
|
||||
if (!seenKeys.add(key)) {
|
||||
duplicateKeys.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!duplicateKeys.isEmpty()) {
|
||||
for (NodeTuple entry : node.getValue()) {
|
||||
Node keyNode = entry.getKeyNode();
|
||||
String key = asScalar(keyNode);
|
||||
if (key!=null && duplicateKeys.contains(key)) {
|
||||
problems.accept(problem(YAML_DUPLICATE_KEY, keyNode, "Duplicate key '"+key+"'"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isIgnoreScalarAssignmentTo(String propName) {
|
||||
//See https://issuetracker.springsource.com/browse/STS-4144
|
||||
return propName!=null && propName.equals("spring.profiles");
|
||||
}
|
||||
|
||||
private void reconcile(NodeTuple entry, IndexNavigator nav) {
|
||||
Node keyNode = entry.getKeyNode();
|
||||
String key = asScalar(keyNode);
|
||||
if (key==null) {
|
||||
expectScalar(keyNode);
|
||||
} else {
|
||||
IndexNavigator subNav = nav.selectSubProperty(key);
|
||||
PropertyInfo match = subNav.getExactMatch();
|
||||
PropertyInfo extension = subNav.getExtensionCandidate();
|
||||
if (match==null && extension==null) {
|
||||
//nothing found for this key. Maybe user is using camelCase variation of the key?
|
||||
String keyAlias = StringUtil.camelCaseToHyphens(key);
|
||||
IndexNavigator subNavAlias = nav.selectSubProperty(keyAlias);
|
||||
match = subNavAlias.getExactMatch();
|
||||
extension = subNavAlias.getExtensionCandidate();
|
||||
if (match!=null || extension!=null) {
|
||||
//Got something for the alias, so use that instead.
|
||||
//Note: do not swap for alias unless we actually found something.
|
||||
// This gives more logical errors (in terms of user's key, not its canonical alias)
|
||||
subNav = subNavAlias;
|
||||
}
|
||||
}
|
||||
if (match!=null && extension!=null) {
|
||||
//This is an odd situation, the current prefix lands on a propery
|
||||
//but there are also other properties that have it as a prefix.
|
||||
//This ambiguity is hard to deal with and we choose not to do so for now
|
||||
return;
|
||||
} else if (match!=null) {
|
||||
Type type = TypeParser.parse(match.getType());
|
||||
if (match.isDeprecated()) {
|
||||
deprecatedProperty(match, keyNode);
|
||||
}
|
||||
reconcile(entry.getValueNode(), type);
|
||||
} else if (extension!=null) {
|
||||
//We don't really care about the extension only about the fact that it
|
||||
// exists and so it is meaningful to continue checking...
|
||||
Node valueNode = entry.getValueNode();
|
||||
reconcile(valueNode, subNav);
|
||||
} else {
|
||||
//both are null, this means there's no valid property with the current prefix
|
||||
//whether exact or extending it with further navigation
|
||||
unkownProperty(keyNode, subNav.getPrefix(), entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconcile a node given the type that we expect the node to be.
|
||||
*/
|
||||
private void reconcile(Node node, Type type) {
|
||||
if (type!=null) {
|
||||
switch (node.getNodeId()) {
|
||||
case scalar:
|
||||
reconcile((ScalarNode)node, type);
|
||||
break;
|
||||
case sequence:
|
||||
reconcile((SequenceNode)node, type);
|
||||
break;
|
||||
case mapping:
|
||||
reconcile((MappingNode)node, type);
|
||||
break;
|
||||
case anchor:
|
||||
//TODO: what should we do with anchor nodes
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Missing switch case");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void reconcile(MappingNode mapping, Type type) {
|
||||
checkForDuplicateKeys(mapping);
|
||||
if (typeUtil.isAtomic(type)) {
|
||||
expectTypeFoundMapping(type, mapping);
|
||||
} else if (TypeUtil.isMap(type) || TypeUtil.isSequencable(type)) {
|
||||
Type keyType = typeUtil.getKeyType(type);
|
||||
Type valueType = TypeUtil.getDomainType(type);
|
||||
if (keyType!=null) {
|
||||
for (NodeTuple entry : mapping.getValue()) {
|
||||
reconcile(entry.getKeyNode(), keyType);
|
||||
}
|
||||
}
|
||||
if (valueType!=null) {
|
||||
for (NodeTuple entry : mapping.getValue()) {
|
||||
Node value = entry.getValueNode();
|
||||
Type nestedValueType = valueType;
|
||||
if (value.getNodeId()==NodeId.mapping) {
|
||||
//Some special cases to handle here!!
|
||||
// See https://issuetracker.springsource.com/browse/STS-4254
|
||||
// See https://issuetracker.springsource.com/browse/STS-4335
|
||||
if (TypeUtil.isObject(valueType)) {
|
||||
nestedValueType = type;
|
||||
} else if (TypeUtil.isString(keyType) && typeUtil.isAtomic(valueType)) {
|
||||
nestedValueType = type;
|
||||
}
|
||||
}
|
||||
reconcile(entry.getValueNode(), nestedValueType);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Neither atomic, map or sequence-like => bean-like
|
||||
Map<String, TypedProperty> props = typeUtil.getPropertiesMap(type, EnumCaseMode.ALIASED, BeanPropertyNameMode.ALIASED);
|
||||
if (props!=null) {
|
||||
for (NodeTuple entry : mapping.getValue()) {
|
||||
Node keyNode = entry.getKeyNode();
|
||||
String key = NodeUtil.asScalar(keyNode);
|
||||
if (key==null) {
|
||||
expectBeanPropertyName(keyNode, type);
|
||||
} else {
|
||||
if (!props.containsKey(key)) {
|
||||
unknownBeanProperty(keyNode, type, key);
|
||||
} else {
|
||||
Node valNode = entry.getValueNode();
|
||||
TypedProperty typedProperty = props.get(key);
|
||||
if (typedProperty!=null) {
|
||||
if (typedProperty.isDeprecated()) {
|
||||
deprecatedProperty(type, typedProperty, keyNode);
|
||||
}
|
||||
reconcile(valNode, typedProperty.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void reconcile(SequenceNode seq, Type type) {
|
||||
if (typeUtil.isAtomic(type)) {
|
||||
expectTypeFoundSequence(type, seq);
|
||||
} else if (TypeUtil.isSequencable(type)) {
|
||||
Type domainType = TypeUtil.getDomainType(type);
|
||||
if (domainType!=null) {
|
||||
for (Node element : seq.getValue()) {
|
||||
reconcile(element, domainType);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
expectTypeFoundSequence(type, seq);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void reconcile(ScalarNode scalar, Type type) {
|
||||
String stringValue = scalar.getValue();
|
||||
if (!stringValue.contains("${")) { //don't check anything with ${} expressions in it as we
|
||||
// don't know its actual value
|
||||
ValueParser valueParser = typeUtil.getValueParser(type);
|
||||
if (valueParser!=null) {
|
||||
// Tag tag = scalar.getTag(); //use the tag? Actually, boot tolerates String values
|
||||
// even if integeger etc are expected. It has its ways of parsing the String to the
|
||||
// expected type
|
||||
try {
|
||||
valueParser.parse(stringValue);
|
||||
} catch (Exception e) {
|
||||
//Couldn't parse
|
||||
valueTypeMismatch(type, scalar);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void expectTypeFoundMapping(Type type, MappingNode node) {
|
||||
expectType(ApplicationYamlProblemType.YAML_EXPECT_TYPE_FOUND_MAPPING, type, node);
|
||||
}
|
||||
|
||||
private void expectTypeFoundSequence(Type type, SequenceNode seq) {
|
||||
expectType(ApplicationYamlProblemType.YAML_EXPECT_TYPE_FOUND_SEQUENCE, type, seq);
|
||||
}
|
||||
|
||||
private void valueTypeMismatch(Type type, ScalarNode scalar) {
|
||||
expectType(ApplicationYamlProblemType.YAML_VALUE_TYPE_MISMATCH, type, scalar);
|
||||
}
|
||||
|
||||
private void unkownProperty(Node node, String name, NodeTuple entry) {
|
||||
SpringPropertyProblem p = problem(ApplicationYamlProblemType.YAML_UNKNOWN_PROPERTY, node, "Unknown property '"+name+"'");
|
||||
p.setPropertyName(extendForQuickfix(StringUtil.camelCaseToHyphens(name), entry.getValueNode()));
|
||||
problems.accept(p);
|
||||
}
|
||||
|
||||
private String extendForQuickfix(String name, Node node) {
|
||||
if (node!=null) {
|
||||
TupleValueRef child = getFirstTupleValue(getChildren(node));
|
||||
if (child!=null) {
|
||||
String extra = NodeUtil.asScalar(child.getKey());
|
||||
if (extra!=null) {
|
||||
return extendForQuickfix(name + "." + StringUtil.camelCaseToHyphens(extra),
|
||||
child.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
//couldn't extend name any further
|
||||
return name;
|
||||
}
|
||||
|
||||
private TupleValueRef getFirstTupleValue(List<NodeRef<?>> children) {
|
||||
for (NodeRef<?> nodeRef : children) {
|
||||
if (nodeRef.getKind()==Kind.VAL) {
|
||||
return (TupleValueRef) nodeRef;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void expectScalar(Node node) {
|
||||
problems.accept(problem(ApplicationYamlProblemType.YAML_EXPECT_SCALAR, node, "Expecting a 'Scalar' node but got "+describe(node)));
|
||||
}
|
||||
|
||||
protected void expectMapping(Node node) {
|
||||
problems.accept(problem(ApplicationYamlProblemType.YAML_EXPECT_MAPPING, node, "Expecting a 'Mapping' node but got "+describe(node)));
|
||||
}
|
||||
|
||||
private void expectBeanPropertyName(Node keyNode, Type type) {
|
||||
problems.accept(problem(ApplicationYamlProblemType.YAML_EXPECT_BEAN_PROPERTY_NAME, keyNode, "Expecting a bean-property name for object of type '"+typeUtil.niceTypeName(type)+"' "
|
||||
+ "but got "+describe(keyNode)));
|
||||
}
|
||||
|
||||
private void unknownBeanProperty(Node keyNode, Type type, String name) {
|
||||
problems.accept(problem(ApplicationYamlProblemType.YAML_INVALID_BEAN_PROPERTY, keyNode, "Unknown property '"+name+"' for type '"+typeUtil.niceTypeName(type)+"'"));
|
||||
}
|
||||
|
||||
private void expectType(ApplicationYamlProblemType problemType, Type type, Node node) {
|
||||
problems.accept(problem(problemType, node, "Expecting a '"+typeUtil.niceTypeName(type)+"' but got "+describe(node)));
|
||||
}
|
||||
|
||||
private void deprecatedProperty(PropertyInfo property, Node keyNode) {
|
||||
SpringPropertyProblem problem = deprecatedPropertyProblem(property.getId(), null, keyNode,
|
||||
property.getDeprecationReplacement(), property.getDeprecationReason());
|
||||
problem.setMetadata(property);
|
||||
problem.setProblemFixer(ReplaceDeprecatedYamlQuickfix.FIXER);
|
||||
problems.accept(problem);
|
||||
}
|
||||
|
||||
private void deprecatedProperty(Type contextType, TypedProperty property, Node keyNode) {
|
||||
SpringPropertyProblem problem = deprecatedPropertyProblem(property.getName(), typeUtil.niceTypeName(contextType),
|
||||
keyNode, property.getDeprecationReplacement(), property.getDeprecationReason());
|
||||
problems.accept(problem);
|
||||
}
|
||||
|
||||
protected SpringPropertyProblem deprecatedPropertyProblem(String name, String contextType, Node keyNode,
|
||||
String replace, String reason) {
|
||||
SpringPropertyProblem problem = problem(YAML_DEPRECATED, keyNode, TypeUtil.deprecatedPropertyMessage(name, contextType, replace, reason));
|
||||
problem.setPropertyName(name);
|
||||
return problem;
|
||||
}
|
||||
|
||||
protected SpringPropertyProblem problem(ApplicationYamlProblemType type, Node node, String msg) {
|
||||
int start = node.getStartMark().getIndex();
|
||||
int end = node.getEndMark().getIndex();
|
||||
return SpringPropertyProblem.problem(type, msg, start, end-start);
|
||||
}
|
||||
|
||||
private String describe(Node node) {
|
||||
switch (node.getNodeId()) {
|
||||
case scalar:
|
||||
return "'"+((ScalarNode)node).getValue()+"'";
|
||||
case mapping:
|
||||
return "a 'Mapping' node";
|
||||
case sequence:
|
||||
return "a 'Sequence' node";
|
||||
case anchor:
|
||||
return "a 'Anchor' node";
|
||||
default:
|
||||
throw new IllegalStateException("Missing switch case");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 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.yaml.reconcile;
|
||||
|
||||
import static org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity.ERROR;
|
||||
import static org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity.WARNING;
|
||||
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
|
||||
|
||||
/**
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public enum ApplicationYamlProblemType implements ProblemType {
|
||||
|
||||
YAML_SYNTAX_ERROR("Error parsing the input using snakeyaml"),
|
||||
YAML_UNKNOWN_PROPERTY(WARNING, "Property-key not found in the configuration metadata on the project's classpath"),
|
||||
YAML_VALUE_TYPE_MISMATCH("Expecting a value of a certain type, but value doesn't parse as such"),
|
||||
YAML_EXPECT_SCALAR("Expecting a 'scalar' value but found something more complex."),
|
||||
YAML_EXPECT_TYPE_FOUND_SEQUENCE("Found a 'sequence' node where a non 'list-like' type is expected"),
|
||||
YAML_EXPECT_TYPE_FOUND_MAPPING("Found a 'mapping' node where a type that can't be treated as a 'property map' is expected"),
|
||||
YAML_EXPECT_MAPPING("Expecting a 'mapping' node but found something else"),
|
||||
YAML_EXPECT_BEAN_PROPERTY_NAME("Expecting a 'bean property' name but found something more complex"),
|
||||
YAML_INVALID_BEAN_PROPERTY("Accessing a named property in a type that doesn't provide a property accessor with that name"),
|
||||
YAML_DEPRECATED(WARNING, "Property is marked as Deprecated"),
|
||||
YAML_DUPLICATE_KEY("A mapping node contains multiple entries for the same key");
|
||||
|
||||
private final ProblemSeverity defaultSeverity;
|
||||
private String description;
|
||||
private String label;
|
||||
|
||||
private ApplicationYamlProblemType(ProblemSeverity defaultSeverity, String description, String label) {
|
||||
this.description = description;
|
||||
this.defaultSeverity = defaultSeverity;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
private ApplicationYamlProblemType(ProblemSeverity defaultSeverity, String description) {
|
||||
this(defaultSeverity, description, null);
|
||||
}
|
||||
|
||||
private ApplicationYamlProblemType(String description) {
|
||||
this(ERROR, description);
|
||||
}
|
||||
|
||||
public ProblemSeverity getDefaultSeverity() {
|
||||
return defaultSeverity;
|
||||
}
|
||||
|
||||
|
||||
public String getLabel() {
|
||||
if (label==null) {
|
||||
label = createDefaultLabel();
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
private String createDefaultLabel() {
|
||||
String label = this.toString().substring(5).toLowerCase().replace('_', ' ');
|
||||
return Character.toUpperCase(label.charAt(0)) + label.substring(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return name();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.springframework.ide.vscode.boot.yaml.reconcile;
|
||||
|
||||
import static org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity.ERROR;
|
||||
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;
|
||||
|
||||
public class ApplicationYamlProblems {
|
||||
|
||||
public static enum Type implements ProblemType {
|
||||
|
||||
YAML_SYNTAX_ERROR;
|
||||
|
||||
Type() {
|
||||
this(ERROR);
|
||||
}
|
||||
|
||||
Type(ProblemSeverity defaultSeverity) {
|
||||
this.severity = defaultSeverity;
|
||||
}
|
||||
|
||||
private final ProblemSeverity severity;
|
||||
|
||||
@Override
|
||||
public ProblemSeverity getDefaultSeverity() {
|
||||
return severity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return name();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final String YAML_SYNTAX_ERROR = "YAML_SYNTAX_ERROR";
|
||||
|
||||
public static ReconcileProblem problem(Type type, String msg, int offset, int len) {
|
||||
return new ReconcileProblemImpl(type, msg, offset, len);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015, 2016 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.yaml.reconcile;
|
||||
|
||||
import static org.springframework.ide.vscode.boot.yaml.reconcile.ApplicationYamlProblems.Type.YAML_SYNTAX_ERROR;
|
||||
|
||||
import org.springframework.ide.vscode.application.properties.metadata.IndexNavigator;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.SpringPropertyIndexProvider;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtilProvider;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.IDocument;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider;
|
||||
import org.springframework.ide.vscode.commons.yaml.reconcile.YamlASTReconciler;
|
||||
import org.springframework.ide.vscode.commons.yaml.reconcile.YamlReconcileEngine;
|
||||
|
||||
public class ApplicationYamlReconcileEngine extends YamlReconcileEngine {
|
||||
|
||||
private SpringPropertyIndexProvider indexProvider;
|
||||
private TypeUtilProvider typeUtilProvider;
|
||||
|
||||
public ApplicationYamlReconcileEngine(YamlASTProvider astProvider, SpringPropertyIndexProvider indexProvider, TypeUtilProvider typeUtilProvider) {
|
||||
super(astProvider);
|
||||
this.indexProvider = indexProvider;
|
||||
this.typeUtilProvider = typeUtilProvider;
|
||||
}
|
||||
|
||||
protected YamlASTReconciler getASTReconciler(IDocument doc, IProblemCollector problemCollector) {
|
||||
FuzzyMap<PropertyInfo> index = indexProvider.getIndex(doc);
|
||||
if (index!=null && !index.isEmpty()) {
|
||||
IndexNavigator nav = IndexNavigator.with(index);
|
||||
return new ApplicationYamlASTReconciler(problemCollector, nav, typeUtilProvider.getTypeUtil(doc));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ReconcileProblem syntaxError(String msg, int offset, int len) {
|
||||
return ApplicationYamlProblems.problem(YAML_SYNTAX_ERROR, msg, offset, len);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.springframework.ide.vscode.boot.yaml.reconcile;
|
||||
|
||||
import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo;
|
||||
import org.springframework.ide.vscode.commons.languageserver.quickfix.ProblemFixer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;
|
||||
|
||||
public class SpringPropertyProblem extends ReconcileProblemImpl {
|
||||
|
||||
private PropertyInfo property = null;
|
||||
private ProblemFixer fixer;
|
||||
private String propertyName;
|
||||
|
||||
public SpringPropertyProblem(ProblemType type, String msg, int offset, int len) {
|
||||
super(type, msg, offset, len);
|
||||
}
|
||||
|
||||
public static SpringPropertyProblem problem(ApplicationYamlProblemType type, String msg, int offset, int len) {
|
||||
return new SpringPropertyProblem(type, msg, offset, len);
|
||||
}
|
||||
|
||||
public void setMetadata(PropertyInfo property) {
|
||||
this.property = property;
|
||||
}
|
||||
|
||||
public void setProblemFixer(ProblemFixer fixer) {
|
||||
this.fixer = fixer;
|
||||
}
|
||||
|
||||
public void setPropertyName(String name) {
|
||||
propertyName = name;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user