From fc1a0fec466a9753f945d97bcdcea09b9f0fc091 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Tue, 18 Jul 2017 10:52:33 -0700 Subject: [PATCH] Reconcile and content assist for ReleaseName references --- .../bosh/BoshDeploymentManifestSchema.java | 11 +++-- .../ide/vscode/concourse/BoshEditorTest.java | 46 ++++++++++++++++++- .../commons/yaml/reconcile/ASTTypeCache.java | 38 +++++++++++++++ .../SchemaBasedYamlASTReconciler.java | 36 +++++++++------ .../commons/yaml/schema/YTypeFactory.java | 20 ++++++-- .../vscode/commons/yaml/schema/YTypeUtil.java | 2 +- .../languageserver/testharness/Editor.java | 26 +++++++++++ 7 files changed, 154 insertions(+), 25 deletions(-) diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java index fcdd17f44..9b2b5b0ba 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java @@ -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); diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshEditorTest.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshEditorTest.java index d7e8fe810..dba66e0e9 100644 --- a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshEditorTest.java +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshEditorTest.java @@ -739,4 +739,48 @@ public class BoshEditorTest { ); } -} \ No newline at end of file + @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]" + ); + } +} diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/ASTTypeCache.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/ASTTypeCache.java index c2b9499c0..f39f5e3b8 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/ASTTypeCache.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/ASTTypeCache.java @@ -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 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 nodes = typeMap.getNodes(defType); + if (nodes!=null) { + ImmutableSet.Builder 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; + } + } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java index 114328423..52d0834e9 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java @@ -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 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); diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java index 8e95a3e3a..5e383636e 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java @@ -209,8 +209,8 @@ public class YTypeFactory { } @Override - public ValueParser getValueParser(YType type, DynamicSchemaContext dc) { - return ((AbstractType)type).getParser(dc); + public SchemaContextAware 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 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> errorMessageFormatter, SchemaContextAware> 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> 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); } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeUtil.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeUtil.java index ca7d2fb7d..7688e9099 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeUtil.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeUtil.java @@ -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 getValueParser(YType type); //TODO: only one of these two should be enough? List getProperties(YType type); diff --git a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java index e97856ed1..9485151e1 100644 --- a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java +++ b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java @@ -383,6 +383,32 @@ public class Editor { } } + public void assertContextualCompletions(LanguageId language, Predicate 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();