Reconcile and content assist for ReleaseName references

This commit is contained in:
Kris De Volder
2017-07-18 10:52:33 -07:00
parent 2ffaed4436
commit fc1a0fec46
7 changed files with 154 additions and 25 deletions

View File

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

View File

@@ -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]"
);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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();