diff --git a/concourse/set-pipeline.sh b/concourse/set-pipeline.sh deleted file mode 100755 index 2fcdaabc5..000000000 --- a/concourse/set-pipeline.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -branch=`git rev-parse --abbrev-ref HEAD` -fly -t tools set-pipeline --var "branch=${branch}" --load-vars-from ${HOME}/.sts4-concourse-credentials.yml -p sts4-${branch} -c pipeline.yml 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 a55994066..ebd1b558f 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 @@ -252,10 +252,9 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler { } //Check for other constraints attached to the type - for (SchemaContextAware _constraint : typeUtil.getConstraints(type)) { - Constraint constraint = _constraint.withContext(dc); + for (Constraint constraint : typeUtil.getConstraints(type)) { if (constraint!=null) { - constraint.verify(dc.getDocument(), parent, map, type, foundProps, problems); + constraint.verify(dc, parent, map, type, problems); } } } 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 f7ed382b7..eabc17ef0 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 @@ -200,7 +200,7 @@ public class YTypeFactory { } @Override - public List> getConstraints(YType type) { + public List getConstraints(YType type) { return ((AbstractType)type).getConstraints(); } }; @@ -218,7 +218,7 @@ public class YTypeFactory { private Map cachedPropertyMap; private SchemaContextAware>> hintProvider; - private List> constraints = new ArrayList<>(2); + private List constraints = new ArrayList<>(2); public boolean isSequenceable() { return false; @@ -282,7 +282,7 @@ public class YTypeFactory { return ImmutableList.of(); } - public List> getConstraints() { + public List getConstraints() { return ImmutableList.copyOf(constraints); } @@ -357,12 +357,12 @@ public class YTypeFactory { return parser == null ? null : parser.withContext(dc); } - public void require(SchemaContextAware dynamicConstraint) { + public void require(Constraint dynamicConstraint) { this.constraints.add(dynamicConstraint); } public void requireOneOf(String... properties) { - this.constraints.add(SchemaContextAware.just(Constraints.requireOneOf(properties))); + this.constraints.add(Constraints.requireOneOf(properties)); } public String[] getPropertyNames() { 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 56ec6d0e4..a78845a29 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 @@ -46,5 +46,5 @@ public interface YTypeUtil { * should be returned. */ YType inferMoreSpecificType(YType type, DynamicSchemaContext dc); - List> getConstraints(YType type); + List getConstraints(YType type); } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/constraints/Constraint.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/constraints/Constraint.java index 74ab5a8a6..3d768b06d 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/constraints/Constraint.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/constraints/Constraint.java @@ -14,6 +14,7 @@ import java.util.Set; import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; import org.springframework.ide.vscode.commons.util.text.IDocument; +import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; import org.springframework.ide.vscode.commons.yaml.schema.YType; import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; @@ -32,12 +33,11 @@ public interface Constraint { * constraint is satisfied. When the constrain is not satisfied they should report any * violations by adding problems to the provide {@link IProblemCollector}. * - * @param map The node being validated + * @param node The node being validated * @param type The inferred type of the node. * @param foundProps The properties this node defines. * @param problems Problem collector where to which the constraint should add the validation problems it finds. */ - void verify(IDocument doc, Node parent, MappingNode map, YType type, Set foundProps, - IProblemCollector problems); + void verify(DynamicSchemaContext dc, Node parent, Node node, YType type, IProblemCollector problems); } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/constraints/Constraints.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/constraints/Constraints.java index fb9195d3f..37b6b4674 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/constraints/Constraints.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/constraints/Constraints.java @@ -24,6 +24,8 @@ import org.springframework.ide.vscode.commons.util.Assert; 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.reconcile.YamlSchemaProblems; +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.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; @@ -62,23 +64,28 @@ public class Constraints { } @Override - public void verify(IDocument doc, Node parent, MappingNode map, YType type, Set foundProps, IProblemCollector problems) { - List requiredProps = Arrays.asList(_requiredProps); - long foundPropsCount = requiredProps.stream() - .filter(foundProps::contains) - .count(); - if (foundPropsCount==0) { - if (!allowFewer) { - problems.accept(missingProperty( - "One of "+requiredProps+" is required for '"+type+"'", doc, parent, map)); - } - } else if (foundPropsCount>1) { - //Mark each of the found keys as a violation: - for (NodeTuple entry : map.getValue()) { - String key = NodeUtil.asScalar(entry.getKeyNode()); - if (key!=null && requiredProps.contains(key)) { - problems.accept(problem(EXTRA_PROPERTY, - "Only one of "+requiredProps+" should be defined for '"+type+"'", entry.getKeyNode())); + public void verify(DynamicSchemaContext dc, Node parent, Node _map, YType type, IProblemCollector problems) { + IDocument doc = dc.getDocument(); + Set foundProps = dc.getDefinedProperties(); + if (_map instanceof MappingNode) { + MappingNode map = (MappingNode) _map; + List requiredProps = Arrays.asList(_requiredProps); + long foundPropsCount = requiredProps.stream() + .filter(foundProps::contains) + .count(); + if (foundPropsCount==0) { + if (!allowFewer) { + problems.accept(missingProperty( + "One of "+requiredProps+" is required for '"+type+"'", doc, parent, map)); + } + } else if (foundPropsCount>1) { + //Mark each of the found keys as a violation: + for (NodeTuple entry : map.getValue()) { + String key = NodeUtil.asScalar(entry.getKeyNode()); + if (key!=null && requiredProps.contains(key)) { + problems.accept(problem(EXTRA_PROPERTY, + "Only one of "+requiredProps+" should be defined for '"+type+"'", entry.getKeyNode())); + } } } } @@ -87,14 +94,32 @@ public class Constraints { public static Constraint deprecated(Function messageFormatter, String... _deprecatedNames) { Set deprecatedNames = ImmutableSet.copyOf(_deprecatedNames); - return (IDocument doc, Node parent, MappingNode map, YType type, Set foundProps, IProblemCollector problems) -> { - for (NodeTuple prop : map.getValue()) { - Node keyNode = prop.getKeyNode(); - String name = NodeUtil.asScalar(keyNode); - if (deprecatedNames.contains(name)) { - problems.accept(YamlSchemaProblems.deprecatedProperty(messageFormatter.apply(name), keyNode)); + return (DynamicSchemaContext dc, Node parent, Node _map, YType type, IProblemCollector problems) -> { + if (_map instanceof MappingNode) { + MappingNode map = (MappingNode) _map; + for (NodeTuple prop : map.getValue()) { + Node keyNode = prop.getKeyNode(); + String name = NodeUtil.asScalar(keyNode); + if (deprecatedNames.contains(name)) { + problems.accept(YamlSchemaProblems.deprecatedProperty(messageFormatter.apply(name), keyNode)); + } } } }; } + + /** + * Deprecated because you shouldn't need to use this method to create a {@link SchemaContextAware} Constraint. + * A Constraint itself is already implicitly aware of the {@link DynamicSchemaContext} (i.e. it already receives + * the {@link DynamicSchemaContext} as a parameter to its verify method. + *

+ * So instead of using this method to get a hold of the {@link DynamicSchemaContext} simply use the context + * passed to your constraint instead. + */ + @Deprecated + public static Constraint schemaContextAware(SchemaContextAware dispatcher) { + return (DynamicSchemaContext dc, Node parent, Node node, YType type, IProblemCollector problems) -> { + dispatcher.withContext(dc).verify(dc, parent, node, type, problems); + }; + } } \ No newline at end of file diff --git a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java index 0021a07df..3eb272045 100644 --- a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java +++ b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java @@ -20,8 +20,8 @@ import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; +import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; 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.SnippetBuilder; import org.springframework.ide.vscode.commons.util.Log; import org.springframework.ide.vscode.commons.util.text.IDocument; @@ -35,10 +35,12 @@ 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.schema.BasicYValueHint; import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; +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; import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty; import org.springframework.ide.vscode.commons.yaml.schema.YValueHint; +import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraint; import org.springframework.ide.vscode.concourse.util.CollectorUtil; import org.springframework.ide.vscode.concourse.util.StaleFallbackCache; import org.yaml.snakeyaml.Yaml; @@ -58,6 +60,14 @@ import com.google.common.collect.Multiset; */ public class ConcourseModel { +// /** +// * Verification of contraint: a job used in the 'passed' attribute of a step +// * must interact with the resource in question. +// */ +// public Constraint jobHasInteractionWithResource() { +// return (IDocument doc, Node parent, Node node, YType type, Set foundProps, IProblemCollector problems) -> { +// } + /** * Wraps around a Node in the AST that represents a 'step' and * provides methods for accessing information from the node. 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 e6fc8fcb1..6b8a99d96 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 @@ -240,7 +240,7 @@ public class PipelineYmlSchema implements YamlSchema { addProp(task, "outputs", f.yseq(t_output)); addProp(task, "run", t_command).isRequired(true); addProp(task, "params", t_string_params); - task.require((dc) -> { + task.require(Constraints.schemaContextAware((DynamicSchemaContext dc) -> { LanguageId languageId = dc.getDocument().getLanguageId(); if (LanguageId.CONCOURSE_PIPELINE.equals(languageId)) { Node parentImageDef = models.getParentPropertyNode("image", dc); @@ -256,7 +256,7 @@ public class PipelineYmlSchema implements YamlSchema { } else { return Constraints.requireAtMostOneOf("image_resource", "image"); } - }); + })); AbstractType t_put_get_name = f.contextAware("Name", (dc) -> { if (models.getParentPropertyNode("resource", dc)!=null) { @@ -287,17 +287,20 @@ public class PipelineYmlSchema implements YamlSchema { addProp(putStep, "get_params", f.contextAware("GetParams", (dc) -> resourceTypes.getInParamsType(getResourceType("put", models, dc)) )); - putStep.require((dc) -> (IDocument doc, Node parent, MappingNode map, YType type, Set foundProps, IProblemCollector problems) -> { - StepModel step = models.newStep("put", map); - String resourceName = step.getResourceName(); - if (resourceName!=null) { - ResourceModel resource = models.getResource(doc, resourceName); - if (resource!=null) { - if ("git".equals(resource.getType()) && !resource.hasSourceProperty("branch")) { - problems.accept(YamlSchemaProblems.schemaProblem( - "Resource of type 'git' is used in a 'put' step, so it should define 'branch' attribute in its 'source', but it doesn't.", - step.getResourceNameNode() - )); + putStep.require((DynamicSchemaContext dc, Node parent, Node _map, YType type, IProblemCollector problems) -> { + if (_map instanceof MappingNode) { + MappingNode map = (MappingNode) _map; + StepModel step = models.newStep("put", map); + String resourceName = step.getResourceName(); + if (resourceName!=null) { + ResourceModel resource = models.getResource(dc.getDocument(), resourceName); + if (resource!=null) { + if ("git".equals(resource.getType()) && !resource.hasSourceProperty("branch")) { + problems.accept(YamlSchemaProblems.schemaProblem( + "Resource of type 'git' is used in a 'put' step, so it should define 'branch' attribute in its 'source', but it doesn't.", + step.getResourceNameNode() + )); + } } } } 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 78b1d5f96..b0d234421 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 @@ -3116,6 +3116,108 @@ public class ConcourseEditorTest { ); } + @Ignore @Test public void reconcilerJobFromPassedAttributeMustInteractWithResource() throws Exception { + Editor editor; + + editor = harness.newEditor( + "resources:\n" + + "- name: version\n" + + " type: semver\n" + + "- name: source-repo\n" + + " type: git\n" + + "jobs:\n" + + "- name: build-it\n" + + " plan:\n" + + " - aggregate:\n" + + " # - put: version\n" + + " - get: source-repo\n" + + "- name: test-it\n" + + " plan:\n" + + " - get: source-repo\n" + + " passed:\n" + + " - build-it # <- good\n" + + " - get: version\n" + + " passed:\n" + + " - build-it # <- bad\n" + ); + editor.assertProblems( + "build-it^ # <- bad|Job 'build-it' doesn't interact with the resource 'version'" + ); + + editor = harness.newEditor( + "resources:\n" + + "- name: version\n" + + " type: semver\n" + + "- name: source-repo\n" + + " type: git\n" + + "jobs:\n" + + "- name: build-it\n" + + " plan:\n" + + " - aggregate:\n" + + " - put: version\n" + + " # - get: source-repo\n" + + "- name: test-it\n" + + " plan:\n" + + " - get: source-repo\n" + + " passed:\n" + + " - build-it # <- bad\n" + + " - get: version\n" + + " passed:\n" + + " - build-it # <- good\n" + ); + editor.assertProblems( + "build-it^ # <- bad|Job 'build-it' doesn't interact with the resource 'source-repo'" + ); + + //Check that we find interactions in steps that are at the top-level of the plan: + editor = harness.newEditor( + "resources:\n" + + "- name: version\n" + + " type: semver\n" + + "- name: source-repo\n" + + " type: git\n" + + "jobs:\n" + + "- name: build-it\n" + + " plan:\n" + + " - aggregate:\n" + + " - put: version\n" + + " - get: source-repo\n" + + "- name: test-it\n" + + " plan:\n" + + " - get: source-repo\n" + + " passed:\n" + + " - build-it\n" + + " - get: version\n" + + " passed:\n" + + " - build-it\n" + ); + editor.assertProblems(/*NONE*/); + + //Check that we find interactions in steps that are nested in other steps + editor = harness.newEditor( + "resources:\n" + + "- name: version\n" + + " type: semver\n" + + "- name: source-repo\n" + + " type: git\n" + + "jobs:\n" + + "- name: build-it\n" + + " plan:\n" + + " - put: version\n" + + " - get: source-repo\n" + + "- name: test-it\n" + + " plan:\n" + + " - get: source-repo\n" + + " passed:\n" + + " - build-it\n" + + " - get: version\n" + + " passed:\n" + + " - build-it\n" + ); + editor.assertProblems(/*NONE*/); + + } + ////////////////////////////////////////////////////////////////////////////// private void assertContextualCompletions(String conText, String textBefore, String... textAfter) throws Exception {