From 3658f87b8431d935495e3c27b65e244d660e791d Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Wed, 13 Dec 2017 13:41:53 -0800 Subject: [PATCH] Fix #28: values 'latest' and 'every' should be accepted for 'version' in getstep --- .../commons/util/PartialCollection.java | 38 ++++++++++ .../yaml/schema/ASTDynamicSchemaContext.java | 15 +++- .../yaml/schema/DynamicSchemaContext.java | 27 ++++++- .../schema/SNodeDynamicSchemaContext.java | 10 +++ .../commons/yaml/schema/YTypeFactory.java | 75 ++++++++++++++++++- .../vscode/concourse/PipelineYmlSchema.java | 10 +-- .../vscode/concourse/ConcourseEditorTest.java | 51 +++++++++++++ 7 files changed, 213 insertions(+), 13 deletions(-) diff --git a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/PartialCollection.java b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/PartialCollection.java index 93efa74777..85e81fcb21 100644 --- a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/PartialCollection.java +++ b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/PartialCollection.java @@ -10,6 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.util; +import static org.assertj.core.api.Assertions.setAllowComparingPrivateFields; + import java.util.Arrays; import java.util.Collection; import java.util.concurrent.Callable; @@ -150,4 +152,40 @@ public Throwable getExplanation() { public PartialCollection add(@SuppressWarnings("unchecked") T... values) { return addAll(Arrays.asList(values)); } + + public PartialCollection addAll(PartialCollection values) { + PartialCollection merged = this.addAll(values.getElements()); + if (!values.isComplete()) { + merged = merged.addUncertainty(); + if (merged.getExplanation()==null && values.getExplanation()!=null) { + merged = merged.withExplanation(values.getExplanation()); + } + } + return merged; + } + + private PartialCollection withExplanation(Throwable explanation) { + return new PartialCollection<>(knownElements, isComplete, explanation); + } + + @Override + public String toString() { + StringBuilder s = new StringBuilder("PartialCollection("); + boolean first = true; + for (T e : knownElements) { + if (!first) { + s.append(", "); + } + s.append(e.toString()); + first = false; + } + if (!isComplete) { + if (!first) { + s.append(", "); + } + s.append("..."); + } + s.append(")"); + return s.toString(); + } } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/ASTDynamicSchemaContext.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/ASTDynamicSchemaContext.java index 86b19137d9..3cd258816a 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/ASTDynamicSchemaContext.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/ASTDynamicSchemaContext.java @@ -18,6 +18,7 @@ import org.springframework.ide.vscode.commons.yaml.path.YamlPath; import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; +import org.yaml.snakeyaml.nodes.ScalarNode; /** * Adapts a SnakeYaml ast node as a {@link DynamicSchemaContext} (so it @@ -30,11 +31,13 @@ public class ASTDynamicSchemaContext extends CachingSchemaContext { private MappingNode mapNode; private YamlPath path; private YamlFileAST ast; + private Node node; public ASTDynamicSchemaContext(YamlFileAST ast, YamlPath path, Node node) { this.ast = ast; this.path = path; this.mapNode = as(MappingNode.class, node); + this.node = node; } @SuppressWarnings("unchecked") @@ -59,9 +62,19 @@ public IDocument getDocument() { public YamlPath getPath() { return path; } - + @Override public YamlFileAST getAST() { return ast; } + + @Override + public boolean isAtomic() { + return node instanceof ScalarNode; + } + + @Override + public boolean isMap() { + return mapNode!=null; + } } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/DynamicSchemaContext.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/DynamicSchemaContext.java index 67e271f6c0..b6d623a49c 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/DynamicSchemaContext.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/DynamicSchemaContext.java @@ -31,7 +31,7 @@ public interface DynamicSchemaContext { DynamicSchemaContext NULL = new DynamicSchemaContext() { - + @Override public Set getDefinedProperties() { return ImmutableSet.of(); @@ -46,7 +46,16 @@ public IDocument getDocument() { public YamlPath getPath() { return null; } - + + @Override + public boolean isAtomic() { + return false; + } + + @Override + public boolean isMap() { + return false; + } }; /** @@ -54,7 +63,7 @@ public YamlPath getPath() { * available (e.g. because of parsing errors) */ default YamlFileAST getAST() { return null; } - + /** * Returns the set of property names that are already defined in the current context. *

@@ -67,7 +76,7 @@ public YamlPath getPath() { * properties are defined in the surrounding object. */ Set getDefinedProperties(); - + /** * Returns the IDocument the current context is in. This allows for some 'schemas' to have * arbitrarily complex analysis of anyhting in the IDocument or even documents related @@ -80,4 +89,14 @@ public YamlPath getPath() { */ YamlPath getPath(); + /** + * Returns true if the current AST node is a scalar value + */ + boolean isAtomic(); + + /** + * Returns true if the current node is a Mapping node + */ + boolean isMap(); + } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/SNodeDynamicSchemaContext.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/SNodeDynamicSchemaContext.java index bfff384982..edd72048ff 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/SNodeDynamicSchemaContext.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/SNodeDynamicSchemaContext.java @@ -74,4 +74,14 @@ public String toString() { return "SNodeDynamicSchemaContext("+contextPath+")"; } + @Override + public boolean isAtomic() { + return false; + } + + @Override + public boolean isMap() { + return false; + } + } 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 c9f287ab68..b4e1cdc04b 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 @@ -24,6 +24,7 @@ import java.util.concurrent.Callable; import java.util.function.BiFunction; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileException; @@ -35,6 +36,7 @@ import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.util.Renderables; import org.springframework.ide.vscode.commons.util.ValueParser; +import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST; import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems; import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraint; import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraints; @@ -153,9 +155,34 @@ public YBeanType ybean(String name, YTypedProperty... properties) { return new YBeanType(name, properties); } - public YBeanUnionType yunion(String name, YBeanType... types) { + public YBeanUnionType yBeanUnion(String name, YBeanType[] types) { + return (YBeanUnionType) yunion(name, types); + } + + public YType yunion(String name, YType... types) { Assert.isLegal(types.length>1); - return new YBeanUnionType(name, types); + if (Stream.of(types).allMatch(t -> t instanceof YBeanType)) { + YBeanType[] beanTypes = new YBeanType[types.length]; + for (int i = 0; i < beanTypes.length; i++) { + beanTypes[i] = (YBeanType) types[i]; + } + return new YBeanUnionType(name, beanTypes); + } + ArrayList maps = new ArrayList<>(types.length); + ArrayList atoms = new ArrayList<>(types.length); + for (YType t : types) { + if (t instanceof YMapType) { + maps.add((YMapType) t); + } else if (t instanceof YAtomicType) { + atoms.add((YAtomicType) t); + } else { + throw new IllegalArgumentException("Union of this kind of types is not (yet) supported: "+t); + } + } + if (atoms.size()==1 && maps.size()==1) { + return new YAtomAndMapUnion(name, atoms.get(0), maps.get(0)); + } + throw new IllegalArgumentException("Union of this kind of types is not (yet) supported: "+types); } /** @@ -763,6 +790,49 @@ public YType inferMoreSpecificType(DynamicSchemaContext dc) { } } + public class YAtomAndMapUnion extends AbstractType { + + private String name; + private YAtomicType atom; + private YMapType map; + + public YAtomAndMapUnion(String name, YAtomicType atom, YMapType map) { + this.name = name; + this.atom = atom; + this.map = map; + } + + @Override + public String toString() { + return name; + } + + @Override + public boolean isAtomic() { + return true; + } + + @Override + public boolean isMap() { + return true; + } + + @Override + public YType inferMoreSpecificType(DynamicSchemaContext dc) { + if (dc.isAtomic()) { + return atom; + } else if (dc.isMap()) { + return map; + } + return super.inferMoreSpecificType(dc); + } + + @Override + public PartialCollection getHintValues(DynamicSchemaContext dc) { + return atom.getHintValues(dc).addAll(map.getHintValues(dc)); + } + + } public static class YTypedPropertyImpl implements YTypedProperty, Cloneable { @@ -1001,5 +1071,4 @@ public YTypeFactory setSnippetProvider(TypeBasedSnippetProvider snippetProvider) return this; } - } diff --git a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java index b97bbf7ceb..61d7a6c477 100644 --- a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java +++ b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java @@ -12,9 +12,7 @@ import java.time.ZoneId; import java.util.ArrayList; -import java.util.Collection; import java.util.List; -import java.util.concurrent.Callable; import java.util.stream.Collectors; import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; @@ -34,7 +32,6 @@ import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems; import org.springframework.ide.vscode.commons.yaml.schema.BasicYValueHint; 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.YTypeFactory; import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.AbstractType; @@ -99,7 +96,10 @@ public class PipelineYmlSchema implements YamlSchema { public final YType t_any = f.yany("Object"); public final YType t_params = f.ymap(t_string, t_any); public final YType t_string_params = f.ymap(t_string, t_string); - public final YType t_resource_version = f.ymap(t_string, t_string); + public final YType t_resource_version = f.yunion("ResourceVersion", + f.yenum("ResourceVersionString", "latest", "every"), + t_string_params + ); public final YType t_pos_integer = f.yatomic("Positive Integer") .parseWith(ValueParsers.POS_INTEGER); public final YType t_strictly_pos_integer = f.yatomic("Strictly Positive Integer") @@ -346,7 +346,7 @@ public PipelineYmlSchema(ConcourseModel models) { doStep, tryStep }; - YBeanUnionType step = f.yunion("Step", stepTypes); + YBeanUnionType step = f.yBeanUnion("Step", stepTypes); addProp(aggregateStep, "aggregate", f.yseq(step)); addProp(doStep, "do", f.yseq(step)); addProp(tryStep, "try", step); diff --git a/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java b/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java index ddbf582504..9de8faadf8 100644 --- a/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java +++ b/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java @@ -4078,6 +4078,27 @@ public void gotoResourceTypeDefinition() throws Exception { ); } + @Test public void getStepVersionShouldAcceptLatestAndEvery() throws Exception { + //See https://github.com/spring-projects/sts4/pull/24 + Editor editor = harness.newEditor( + "jobs:\n" + + "- name: do-stuff\n" + + " plan:\n" + + " - get: cf-deployment-git\n" + + " version: latest\n" + + " - get: some-resource\n" + + " version: every\n" + + " - get: other-resource\n" + + " version: bogus" + ); + editor.assertProblems( + "cf-deployment-git|resource does not exist", + "some-resource|resource does not exist", + "other-resource|resource does not exist", + "bogus|Valid values are: [every, latest]" + ); + } + @Test public void getStepVersionShouldAcceptMap() throws Exception { //See https://github.com/spring-projects/sts4/pull/24 Editor editor = harness.newEditor( @@ -4090,6 +4111,36 @@ public void gotoResourceTypeDefinition() throws Exception { editor.assertProblems("cf-deployment-git|resource does not exist"); } + @Test public void getStepVersionCompletionsSuggestLatestAndEvery() throws Exception { + //See https://github.com/spring-projects/sts4/pull/24 + Editor editor = harness.newEditor( + "jobs:\n" + + "- name: do-stuff\n" + + " plan:\n" + + " - get: cf-deployment-git\n" + + " version: <*>" + ); + editor.assertCompletionLabels("every", "latest"); + } + + @Test public void getStepVersionMapStringStringValidation() throws Exception { + //See https://github.com/spring-projects/sts4/pull/24 + Editor editor = harness.newEditor( + "jobs:\n" + + "- name: do-stuff\n" + + " plan:\n" + + " - get: cf-deployment-git\n" + + " version: { ref: good}\n" + + " - get: other-rsrc\n" + + " version: { ref: [bad]}\n" + ); + editor.assertProblems( + "cf-deployment-git|resource does not exist", + "other-rsrc|resource does not exist", + "[bad]|Expecting a 'String' but found a 'Sequence'" + ); + } + ////////////////////////////////////////////////////////////////////////////// private void assertContextualCompletions(String conText, String textBefore, String... textAfter) throws Exception {