Reconcile and content assist for ReleaseName references
This commit is contained in:
@@ -73,6 +73,7 @@ public class BoshDeploymentManifestSchema implements YamlSchema {
|
||||
private YType t_instance_group_name_def;
|
||||
private YType t_stemcell_alias_name_def;
|
||||
private YType t_release_name_def;
|
||||
private YType t_release_name_ref;
|
||||
private YType t_var_name_def;
|
||||
private final ASTTypeCache astTypes;
|
||||
|
||||
@@ -114,10 +115,13 @@ public class BoshDeploymentManifestSchema implements YamlSchema {
|
||||
|
||||
t_instance_group_name_def = f.yatomic("InstanceGroupName")
|
||||
.parseWith(ValueParsers.NE_STRING);
|
||||
|
||||
t_stemcell_alias_name_def = f.yatomic("StemcellAliasName")
|
||||
.parseWith(ValueParsers.NE_STRING);
|
||||
|
||||
t_release_name_def = f.yatomic("ReleaseName")
|
||||
.parseWith(ValueParsers.NE_STRING);
|
||||
t_release_name_ref = f.yenumFromDynamicValues("ReleaseName", (dc) -> astTypes.getDefinedNames(dc, t_release_name_def));
|
||||
t_var_name_def = f.yatomic("VariableName")
|
||||
.parseWith(ValueParsers.NE_STRING);
|
||||
|
||||
@@ -167,8 +171,9 @@ public class BoshDeploymentManifestSchema implements YamlSchema {
|
||||
addProp(t_release, "url", t_url);
|
||||
addProp(t_release, "sha1", t_ne_string);
|
||||
addProp(v2Schema, "releases", f.yseq(t_release)).isRequired(true);
|
||||
t_release.require(Constraints.requireAtLeastOneOf("url", "version")); //allthough docs seem to imply you shouldn't define both url and version..
|
||||
//... it seems bosh tolerates it.
|
||||
t_release.require(Constraints.requireAtLeastOneOf("url", "version"));
|
||||
// ^^^^^^^ allthough docs seem to imply you shouldn't
|
||||
// define both url and version it seems that bosh tolerates it.
|
||||
t_release.require(BoshConstraints.SHA1_REQUIRED_FOR_HTTP_URL);
|
||||
|
||||
YBeanType t_stemcell = f.ybean("Stemcell");
|
||||
@@ -189,7 +194,7 @@ public class BoshDeploymentManifestSchema implements YamlSchema {
|
||||
|
||||
YBeanType t_job = f.ybean("Job");
|
||||
addProp(t_job, "name", t_ne_string).isRequired(true);
|
||||
addProp(t_job, "release", t_ne_string).isRequired(true);
|
||||
addProp(t_job, "release", t_release_name_ref).isRequired(true);
|
||||
addProp(t_job, "consumes", t_params);
|
||||
addProp(t_job, "provides", t_params);
|
||||
addProp(t_job, "properties", t_params);
|
||||
|
||||
@@ -739,4 +739,48 @@ public class BoshEditorTest {
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@Test public void contentAssistReleaseReference() throws Exception {
|
||||
Editor editor = harness.newEditor(
|
||||
"name: foo\n" +
|
||||
"instance_groups: \n" +
|
||||
"- name: some-server\n" +
|
||||
" jobs:\n" +
|
||||
" - release: <*>\n" +
|
||||
"releases: \n" +
|
||||
"- name: some-release\n" +
|
||||
" url: https://release-hub.info/some-release.tar.gz?version=99.3.2\n" +
|
||||
" sha1: asddsfsd\n" +
|
||||
"- name: other-release\n" +
|
||||
" url: https://release-hub.info/other-release.tar.gz?version=99.3.2\n" +
|
||||
" sha1: asddsfsd\n"
|
||||
);
|
||||
editor.assertContextualCompletions("<*>"
|
||||
, // ==>
|
||||
"other-release<*>", "some-release<*>"
|
||||
);
|
||||
}
|
||||
|
||||
@Test public void reconcileReleaseReference() throws Exception {
|
||||
Editor editor = harness.newEditor(
|
||||
"name: foo\n" +
|
||||
"instance_groups: \n" +
|
||||
"- name: some-server\n" +
|
||||
" jobs:\n" +
|
||||
" - release: some-release\n" +
|
||||
"- name: some-other-server\n" +
|
||||
" jobs:\n" +
|
||||
" - release: bogus-release\n" +
|
||||
"releases: \n" +
|
||||
"- name: some-release\n" +
|
||||
" url: https://release-hub.info/some-release.tar.gz?version=99.3.2\n" +
|
||||
" sha1: asddsfsd\n" +
|
||||
"- name: other-release\n" +
|
||||
" url: https://release-hub.info/other-release.tar.gz?version=99.3.2\n" +
|
||||
" sha1: asddsfsd\n"
|
||||
);
|
||||
editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY);
|
||||
editor.assertProblems(
|
||||
"bogus-release|unknown 'ReleaseName'. Valid values are: [other-release, some-release]"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.yaml.reconcile;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -18,12 +19,21 @@ import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
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.schema.DynamicSchemaContext;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YType;
|
||||
import org.yaml.snakeyaml.nodes.Node;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.ImmutableMultiset;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSet.Builder;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
/**
|
||||
@@ -126,5 +136,33 @@ public class ASTTypeCache implements ITypeCollector {
|
||||
return typeIndex.get(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this astTypeCache to extract all defined names for a given type of definition.
|
||||
*/
|
||||
public Collection<String> getDefinedNames(DynamicSchemaContext dc, YType defType) {
|
||||
IDocument doc = dc.getDocument();
|
||||
if (doc!=null) {
|
||||
String uri = doc.getUri();
|
||||
if (uri!=null) {
|
||||
NodeTypes typeMap = getNodeTypes(uri);
|
||||
if (typeMap!=null) {
|
||||
Collection<Node> nodes = typeMap.getNodes(defType);
|
||||
if (nodes!=null) {
|
||||
ImmutableSet.Builder<String> builder = ImmutableSortedSet.naturalOrder();
|
||||
for (Node node : nodes) {
|
||||
String name = NodeUtil.asScalar(node);
|
||||
if (StringUtil.hasText(name)) {
|
||||
builder.add(name);
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
return ImmutableList.of();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment;
|
||||
import org.springframework.ide.vscode.commons.yaml.quickfix.YamlQuickfixes;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.ASTDynamicSchemaContext;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.SchemaContextAware;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YType;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty;
|
||||
@@ -129,11 +130,11 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
|
||||
return allOf(ast, node);
|
||||
}
|
||||
|
||||
private void reconcile(YamlFileAST ast, YamlPath path, Node parent, Node node, YType type) {
|
||||
private void reconcile(YamlFileAST ast, YamlPath path, Node parent, Node node, YType _type) {
|
||||
// IDocument doc = ast.getDocument();
|
||||
if (type!=null) {
|
||||
if (_type!=null) {
|
||||
DynamicSchemaContext schemaContext = new ASTDynamicSchemaContext(ast, path, node);
|
||||
type = typeUtil.inferMoreSpecificType(type, schemaContext);
|
||||
YType type = typeUtil.inferMoreSpecificType(_type, schemaContext);
|
||||
if (typeCollector!=null) {
|
||||
typeCollector.accept(node, type);
|
||||
}
|
||||
@@ -190,19 +191,24 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
|
||||
break;
|
||||
case scalar:
|
||||
if (typeUtil.isAtomic(type)) {
|
||||
ValueParser parser = typeUtil.getValueParser(type, schemaContext);
|
||||
if (parser!=null) {
|
||||
try {
|
||||
String value = NodeUtil.asScalar(node);
|
||||
if (value!=null) {
|
||||
parser.parse(value);
|
||||
SchemaContextAware<ValueParser> parserProvider = typeUtil.getValueParser(type);
|
||||
if (parserProvider!=null) {
|
||||
delayedConstraints.add(() -> {
|
||||
ValueParser parser = parserProvider.withContext(schemaContext);
|
||||
if (parser!=null) {
|
||||
try {
|
||||
String value = NodeUtil.asScalar(node);
|
||||
if (value!=null) {
|
||||
parser.parse(value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ProblemType problemType = getProblemType(e);
|
||||
DocumentRegion region = getRegion(e, ast.getDocument(), node);
|
||||
String msg = getMessage(e);
|
||||
valueParseError(type, region, msg, problemType, getValueReplacement(e));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ProblemType problemType = getProblemType(e);
|
||||
DocumentRegion region = getRegion(e, ast.getDocument(), node);
|
||||
String msg = getMessage(e);
|
||||
valueParseError(type, region, msg, problemType, getValueReplacement(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
expectTypeButFoundScalar(type, node);
|
||||
|
||||
@@ -209,8 +209,8 @@ public class YTypeFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueParser getValueParser(YType type, DynamicSchemaContext dc) {
|
||||
return ((AbstractType)type).getParser(dc);
|
||||
public SchemaContextAware<ValueParser> getValueParser(YType type) {
|
||||
return ((AbstractType)type).getParser();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -406,8 +406,8 @@ public class YTypeFactory {
|
||||
parseWith((DynamicSchemaContext dc) -> parser);
|
||||
return this;
|
||||
}
|
||||
private ValueParser getParser(DynamicSchemaContext dc) {
|
||||
return parser == null ? null : parser.withContext(dc);
|
||||
private SchemaContextAware<ValueParser> getParser() {
|
||||
return parser;
|
||||
}
|
||||
|
||||
public AbstractType require(Constraint dynamicConstraint) {
|
||||
@@ -847,7 +847,6 @@ public class YTypeFactory {
|
||||
return ((YTypedPropertyImpl)prop).copy();
|
||||
}
|
||||
|
||||
|
||||
public YAtomicType yenumFromHints(String name, BiFunction<String, Collection<String>, String> errorMessageFormatter, SchemaContextAware<Collection<YValueHint>> values) {
|
||||
YAtomicType t = yatomic(name);
|
||||
t.addHintProvider((dc) -> () -> values.withContext(dc));
|
||||
@@ -863,6 +862,17 @@ public class YTypeFactory {
|
||||
return t;
|
||||
}
|
||||
|
||||
public YAtomicType yenumFromDynamicValues(String name, SchemaContextAware<Collection<String>> values) {
|
||||
return yenumFromHints(name,
|
||||
//Error message formatter:
|
||||
(parseString, validValues) -> "'"+parseString+"' is an unknown '"+name+"'. Valid values are: "+validValues,
|
||||
//Hints provider:
|
||||
(dc) ->
|
||||
hints(values.withContext(dc)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public EnumTypeBuilder yenumBuilder(String name, String... values) {
|
||||
return new EnumTypeBuilder(name, values);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public interface YTypeUtil {
|
||||
YValueHint[] getHintValues(YType yType, DynamicSchemaContext dc) throws Exception;
|
||||
String niceTypeName(YType type);
|
||||
YType getKeyType(YType type);
|
||||
ValueParser getValueParser(YType type, DynamicSchemaContext dc);
|
||||
SchemaContextAware<ValueParser> getValueParser(YType type);
|
||||
|
||||
//TODO: only one of these two should be enough?
|
||||
List<YTypedProperty> getProperties(YType type);
|
||||
|
||||
@@ -383,6 +383,32 @@ public class Editor {
|
||||
}
|
||||
}
|
||||
|
||||
public void assertContextualCompletions(LanguageId language, Predicate<CompletionItem> isInteresting, String textBefore, String... textAfter) throws Exception {
|
||||
Editor editor = harness.newEditor(language, this.getText());
|
||||
editor.reconcile(); //this ensures the conText is parsed and its AST is cached (will be used for
|
||||
//dynamic CA when the conText + textBefore is not parsable.
|
||||
|
||||
textBefore = replaceSelection(textBefore);
|
||||
textAfter = Arrays.stream(textAfter)
|
||||
.map((String t) -> replaceSelection(t))
|
||||
.collect(Collectors.toList()).toArray(new String[0]);
|
||||
editor.setText(textBefore);
|
||||
editor.assertCompletions(isInteresting, textAfter);
|
||||
}
|
||||
|
||||
public void assertContextualCompletions(String textBefore, String... textAfter) throws Exception {
|
||||
assertContextualCompletions(getLanguageId(), (x) -> true, textBefore, textAfter);
|
||||
}
|
||||
|
||||
private String replaceSelection(String replacement) {
|
||||
try {
|
||||
String text = getRawText();
|
||||
return text.substring(0, selectionStart) + replacement + text.substring(selectionEnd);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void apply(CompletionItem completion) throws Exception {
|
||||
completion = harness.resolveCompletionItem(completion);
|
||||
TextEdit edit = completion.getTextEdit();
|
||||
|
||||
Reference in New Issue
Block a user