diff --git a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/ast/NodeUtil.java b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/ast/NodeUtil.java index 954ec7d27..16cea3fd0 100644 --- a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/ast/NodeUtil.java +++ b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/ast/NodeUtil.java @@ -98,4 +98,19 @@ public class NodeUtil { return Collections.emptySet(); } + public static String getScalarProperty(Node node, String propName) { + if (node instanceof MappingNode) { + for (NodeTuple entry : ((MappingNode)node).getValue()) { + String key = NodeUtil.asScalar(entry.getKeyNode()); + if (propName.equals(key)) { + String value = asScalar(entry.getValueNode()); + if (value!=null) { + return value; + } + } + } + } + return null; + } + } diff --git a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java index be524f4b6..f7938e274 100644 --- a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java +++ b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java @@ -46,7 +46,7 @@ public class YTypeFactory { return new YContextSensitive(name, guessType); } - public YType yany(String name) { + public AbstractType yany(String name) { return new YAny(name); } @@ -246,7 +246,7 @@ public class YTypeFactory { public void addProperty(String name, YType type) { addProperty(new YTypedPropertyImpl(name, type)); } - public void addHints(String... strings) { + public AbstractType addHints(String... strings) { if (strings != null) { for (String value : strings) { BasicYValueHint hint = new BasicYValueHint(value); @@ -255,6 +255,7 @@ public class YTypeFactory { } } } + return this; } public void addHints(YValueHint... extraHints) { @@ -269,8 +270,9 @@ public class YTypeFactory { this.parser = parser; } - public void parseWith(ValueParser parser) { + public AbstractType parseWith(ValueParser parser) { parseWith((DynamicSchemaContext dc) -> parser); + return this; } private ValueParser getParser(DynamicSchemaContext dc) { return parser == null ? null : parser.withContext(dc); diff --git a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java index 1a5731099..00642746b 100644 --- a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java +++ b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java @@ -23,7 +23,10 @@ import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil; import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider; import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST; import org.springframework.ide.vscode.commons.yaml.ast.YamlParser; +import org.springframework.ide.vscode.commons.yaml.path.ASTRootCursor; +import org.springframework.ide.vscode.commons.yaml.path.NodeCursor; import org.springframework.ide.vscode.commons.yaml.path.YamlPath; +import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment; import org.springframework.ide.vscode.concourse.util.CollectorUtil; import org.springframework.ide.vscode.concourse.util.StaleFallbackCache; import org.yaml.snakeyaml.Yaml; @@ -33,12 +36,12 @@ import org.yaml.snakeyaml.nodes.Node; import com.google.common.collect.Multiset; /** - * ConcourseModels is responsible for extracting various bits of information + * ConcourseModels is responsible for extracting various bits of information * out of .yml documents and caching them for use by various tools (reconcile engine * and completion engine). */ public class ConcourseModel { - + private static final YamlPath RESOURCE_NAMES_PATH = new YamlPath( valueAt("resources"), anyChild(), @@ -50,7 +53,13 @@ public class ConcourseModel { anyChild(), valueAt("name") ); - + + private static final YamlPath RESOURCES_FROM_ROOT_PATH = new YamlPath( + anyChild(), // skip over the root node which contains multiple doces + valueAt("resources"), + anyChild() + ); + private final YamlParser parser; private final StaleFallbackCache asts = new StaleFallbackCache<>(); @@ -66,7 +75,7 @@ public class ConcourseModel { asts.invalidate(uri); } } - + /** * Returns the resource names that are defined by given IDocument. If the contents * of IDocument is not currently parseable then this may return stale information @@ -80,7 +89,29 @@ public class ConcourseModel { return getStringsFromAst(doc, RESOURCE_NAMES_PATH); } - + /** + * Get the resource type tag associated with a given resourceName in the given document. + *

+ * If the content of IDocument is not currently parseable then this may return stale information + * retained from a previous successful parse. + *

+ * It may also return null if its not currently possible to obtain type of the resource. E.g + * because there is no such resource, the resource has no valid type tag, or the document + * was never successfully parsed. + */ + public String getResourceType(IDocument doc, String resourceName) { + return getFromAst(doc, (ast) -> { + Node resource = RESOURCES_FROM_ROOT_PATH.traverseAmbiguously(new ASTRootCursor(ast)) + .map((cursor) -> ((NodeCursor)cursor).getNode()) + .filter((resourceNode) -> resourceName.equals(NodeUtil.getScalarProperty(resourceNode, "name"))) + .findFirst().orElse(null); + if (resource!=null) { + return NodeUtil.getScalarProperty(resource, "type"); + } + return null; + }); + } + /** * Returns the job names that are defined by given IDocument. If the contents * of IDocument is not currently parseable then this may return stale information @@ -104,14 +135,14 @@ public class ConcourseModel { .collect(CollectorUtil.toMultiset()); }); } - - private T getFromAst(IDocument doc, Function getResourceNames) { + + private T getFromAst(IDocument doc, Function astFunction) { try { if (doc!=null) { String uri = doc.getUri(); if (uri!=null) { YamlFileAST ast = getAst(doc); - return getResourceNames.apply(ast); + return astFunction.apply(ast); } } } catch (YAMLException e) { @@ -146,5 +177,4 @@ public class ConcourseModel { }; } - } diff --git a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java index dc901fd29..4534de454 100644 --- a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java +++ b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java @@ -12,6 +12,7 @@ package org.springframework.ide.vscode.concourse; import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.util.Renderables; +import org.springframework.ide.vscode.commons.util.StringUtil; import org.springframework.ide.vscode.commons.util.ValueParsers; import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil; import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST; @@ -50,6 +51,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_pos_integer = f.yatomic("Positive Integer") + .parseWith(ValueParsers.POS_INTEGER); + public final YType t_strictly_pos_integer = f.yatomic("Strictly Positive Integer") + .parseWith(ValueParsers.integerAtLeast(1)); private final ResourceTypeRegistry resourceTypes = new ResourceTypeRegistry(); @@ -61,10 +66,6 @@ public class PipelineYmlSchema implements YamlSchema { YAtomicType t_ne_string = f.yatomic("String"); t_ne_string.parseWith(ValueParsers.NE_STRING); - YAtomicType t_pos_integer = f.yatomic("Positive Integer"); - t_pos_integer.parseWith(ValueParsers.POS_INTEGER); - YAtomicType t_strictly_pos_integer = f.yatomic("Strictly Positive Integer"); - t_strictly_pos_integer.parseWith(ValueParsers.integerAtLeast(1)); YAtomicType t_duration = f.yatomic("Duration"); t_duration.parseWith(ConcourseValueParsers.DURATION); @@ -131,7 +132,9 @@ public class PipelineYmlSchema implements YamlSchema { addProp(getStep, "resource", t_string); addProp(getStep, "version", t_version); addProp(getStep, "passed", f.yseq(jobName)); - addProp(getStep, "params", t_params); + addProp(getStep, "params", f.contextAware("GetParams", (dc) -> + resourceTypes.getInParamsType(getResourceType("get", models, dc)) + )); addProp(getStep, "trigger", t_boolean); YBeanType putStep = f.ybean("PutStep"); @@ -216,6 +219,7 @@ public class PipelineYmlSchema implements YamlSchema { } private void initializeDefaultResourceTypes() { + // git resource YBeanType gitSource = f.ybean("GitResourceSource"); addProp(gitSource, "uri", t_string).isRequired(true); addProp(gitSource, "branch", t_string).isRequired(true); @@ -232,15 +236,34 @@ public class PipelineYmlSchema implements YamlSchema { addProp(gitSource, "commit_verification_key_ids", t_strings); addProp(gitSource, "gpg_keyserver", t_string); - resourceTypes.def("git", gitSource); + YBeanType gitGetParams = f.ybean("GitGetParams"); + addProp(gitGetParams, "depth", t_pos_integer); + addProp(gitGetParams, "submodules", f.yany("GitSubmodules").addHints("all", "none")); + addProp(gitGetParams, "disable_git_lfs", t_boolean); + + YBeanType gitPutParams = f.ybean("GitPutParams"); + resourceTypes.def("git", gitSource, gitGetParams, gitPutParams); + + } + + private String getResourceType(String resourceNameProp, ConcourseModel models, DynamicSchemaContext dc) { + String resourceName = getParentPropertyValue(resourceNameProp, models, dc); + if (resourceName!=null) { + return models.getResourceType(dc.getDocument(), resourceName); + } + return null; } private String getResourceTypeTag(ConcourseModel models, DynamicSchemaContext dc) { + return getParentPropertyValue("type", models, dc); + } + + private String getParentPropertyValue(String propName, ConcourseModel models, DynamicSchemaContext dc) { YamlPath path = dc.getPath(); if (path!=null) { YamlFileAST root = models.getSafeAst(dc.getDocument()); if (root!=null) { - return NodeUtil.asScalar(path.dropLast().append(YamlPathSegment.valueAt("type")).traverseToNode(root)); + return NodeUtil.asScalar(path.dropLast().append(YamlPathSegment.valueAt(propName)).traverseToNode(root)); } } return null; diff --git a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ResourceTypeRegistry.java b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ResourceTypeRegistry.java index 62b3a9fb8..43483b5c5 100644 --- a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ResourceTypeRegistry.java +++ b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ResourceTypeRegistry.java @@ -25,19 +25,58 @@ import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanType */ public class ResourceTypeRegistry { - private Map sourceTypes = new HashMap<>(); + private static class ResourceTypeInfo { + private final YBeanType source; + private final YBeanType in; + private final YBeanType out; + + public ResourceTypeInfo(YBeanType source, YBeanType in, YBeanType out) { + super(); + this.source = source; + this.in = in; + this.out = out; + } + + public YBeanType getSource() { + return source; + } + + public YBeanType getIn() { + return in; + } + + public YBeanType getOut() { + return out; + } + } + + private Map resourceTypes = new HashMap<>(); + public ResourceTypeRegistry() { } - public void def(String resourceTypeName, YBeanType sourceType) { - Assert.isLegal(!sourceTypes.containsKey(resourceTypeName), "Multiple definitions for '"+resourceTypeName+"'"); - sourceTypes.put(resourceTypeName, sourceType); + public void def(String resourceTypeName, YBeanType source, YBeanType in, YBeanType out) { + Assert.isLegal(!resourceTypes.containsKey(resourceTypeName), "Multiple definitions for '"+resourceTypeName+"'"); + resourceTypes.put(resourceTypeName, new ResourceTypeInfo(source, in, out)); } public YType getSourceType(String typeTag) { if (typeTag!=null) { - return sourceTypes.get(typeTag); + ResourceTypeInfo v = resourceTypes.get(typeTag); + if (v!=null) { + return v.getSource(); + } + } + return null; + } + + public YType getInParamsType(String typeTag) { + if (typeTag!=null) { + ResourceTypeInfo v = resourceTypes.get(typeTag); + if (v!=null) { + return v.getIn(); + } } return null; } diff --git a/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java b/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java index dd59454ea..d0608e3d4 100644 --- a/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java +++ b/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java @@ -778,6 +778,62 @@ public class PipelineYamlEditorTest { editor.assertHoverContains("commit_verification_key_ids", "Array of GPG public key ids"); } + @Test public void gitResourceGetParamsCompletions() throws Exception { + String context = + "resources:\n" + + "- name: my-git\n" + + " type: git\n" + + "jobs:\n" + + "- name: do-stuff\n" + + " plan:\n" + + " - get: my-git\n" + + " params:\n" + + " <*>"; + + assertContextualCompletions(context, + "<*>" + , // ===> + "depth: <*>" + , + "disable_git_lfs: <*>" + , + "submodules:\n"+ + " <*>" + ); + assertContextualCompletions(context, + "disable_git_lfs: <*>" + , // ===> + "disable_git_lfs: false<*>", + "disable_git_lfs: true<*>" + ); + assertContextualCompletions(context, + "submodules: <*>" + , // ===> + "submodules: all<*>", + "submodules: none<*>" + ); + } + + @Test public void gitResourceGetParamsReconcile() throws Exception { + Editor editor = harness.newEditor( + "resources:\n" + + "- name: my-git\n" + + " type: git\n" + + "jobs:\n" + + "- name: do-stuff\n" + + " plan:\n" + + " - get: my-git\n" + + " params:\n" + + " depth: -1\n" + + " disable_git_lfs: not-bool\n" + ); + editor.assertProblems( + "-1|must be positive", + "not-bool|'boolean'" + ); + } + + @Test public void contentAssistJobNames() throws Exception { assertContextualCompletions(