diff --git a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ValueParsers.java b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ValueParsers.java index 5020cbdfd..ccc7cf016 100644 --- a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ValueParsers.java +++ b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ValueParsers.java @@ -24,7 +24,7 @@ public class ValueParsers { throw new ValueParseException("String should not be empty"); } }; - + public static final ValueParser POS_INTEGER = integerRange(0, null); public static ValueParser integerAtLeast(final Integer lowerBound) { diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlSchemaProblems.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlSchemaProblems.java index 81833a114..9bf854042 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlSchemaProblems.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlSchemaProblems.java @@ -55,7 +55,7 @@ public class YamlSchemaProblems { public static final ProblemType DEPRECATED_VALUE = problemType("DeprecatedValue", ProblemSeverity.WARNING); public static final ProblemType MISSING_PROPERTY = problemType("MissingProperty", ProblemSeverity.ERROR); public static final ProblemType EXTRA_PROPERTY = problemType("ExtraProperty", ProblemSeverity.ERROR); - + public static final ProblemType EMPTY_OPTIONAL_STRING = problemType("EmptyOptionalString", ProblemSeverity.WARNING); public static final Set PROPERTY_CONSTRAINT = ImmutableSet.of( MISSING_PROPERTY, EXTRA_PROPERTY diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlSchemaValueParsers.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlSchemaValueParsers.java new file mode 100644 index 000000000..de9d98685 --- /dev/null +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlSchemaValueParsers.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2018 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.yaml.reconcile; + +import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileException; +import org.springframework.ide.vscode.commons.util.StringUtil; +import org.springframework.ide.vscode.commons.util.ValueParser; + +/** + * Reusable value parsers and helpers that are somewhat specific + * to yaml schema validation. + */ +public class YamlSchemaValueParsers { + + public static final ValueParser OPT_STRING = (s) -> { + if (StringUtil.hasText(s)) { + return s; + } else { + throw new ReconcileException("Empty optional String attribute is useless and can be omitted.", YamlSchemaProblems.EMPTY_OPTIONAL_STRING); + } + }; +} diff --git a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseValueParsers.java b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseValueParsers.java index c7b012e85..a0564e2ac 100644 --- a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseValueParsers.java +++ b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseValueParsers.java @@ -106,5 +106,4 @@ public class ConcourseValueParsers { return time + pm + zone; } - } 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 5757d21a9..29aa9e026 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 @@ -31,6 +31,7 @@ import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST; 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.reconcile.YamlSchemaProblems; +import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaValueParsers; 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; @@ -88,6 +89,8 @@ public class PipelineYmlSchema implements YamlSchema { public final YType t_string = f.yatomic("String"); public final YType t_ne_string = f.yatomic("String") .parseWith(ValueParsers.NE_STRING); + public final YType t_opt_string = f.yatomic("String") + .parseWith(YamlSchemaValueParsers.OPT_STRING); public final YType t_strings = f.yseq(t_string); public final YType t_pair = f.ybean("NameValuePair", @@ -248,11 +251,12 @@ public class PipelineYmlSchema implements YamlSchema { AbstractType t_input = f.ybean("TaskInput"); addProp(t_input, "name", t_ne_string).isPrimary(true); - addProp(t_input, "path", t_ne_string); + addProp(t_input, "path", t_opt_string); + addProp(t_input, "optional", t_boolean); AbstractType t_output = f.ybean("TaskOutput"); addProp(t_output, "name", t_ne_string).isPrimary(true); - addProp(t_output, "path", t_ne_string); + addProp(t_output, "path", t_opt_string); AbstractType t_command = f.ybean("Command"); addProp(t_command, "path", t_ne_string).isRequired(true); diff --git a/headless-services/concourse-language-server/src/main/resources/desc/TaskInput/optional.md b/headless-services/concourse-language-server/src/main/resources/desc/TaskInput/optional.md new file mode 100644 index 000000000..7c62d3cfe --- /dev/null +++ b/headless-services/concourse-language-server/src/main/resources/desc/TaskInput/optional.md @@ -0,0 +1,3 @@ +*Optional*. If `true`, then the input is not required by the task. The task may run even if this input is missing. + +An optional input that is missing will not appear in the current directory of the running task. \ No newline at end of file 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 eac1119c8..0e30f3b89 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 @@ -12,7 +12,6 @@ package org.springframework.ide.vscode.concourse; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import static org.mockito.Mockito.when; import static org.springframework.ide.vscode.languageserver.testharness.Editor.INDENTED_COMPLETION; import static org.springframework.ide.vscode.languageserver.testharness.Editor.PLAIN_COMPLETION; @@ -31,7 +30,7 @@ import org.eclipse.lsp4j.DiagnosticSeverity; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; -import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity; +import org.mockito.Mockito; import org.springframework.ide.vscode.commons.util.IOUtil; import org.springframework.ide.vscode.commons.util.Unicodes; import org.springframework.ide.vscode.commons.util.text.LanguageId; @@ -47,21 +46,20 @@ import org.springframework.ide.vscode.languageserver.testharness.Synchronization import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import org.mockito.Mockito; - public class ConcourseEditorTest { private static final YamlCompletionEngineOptions OPTIONS = YamlCompletionEngineOptions.TEST_DEFAULT; private static final String CURSOR = "<*>"; - LanguageServerHarness harness; + LanguageServerHarness harness; private GithubInfoProvider github= Mockito.mock(GithubInfoProvider.class); @Before public void setup() throws Exception { - harness = new LanguageServerHarness(() -> { - return new ConcourseLanguageServer(OPTIONS, github) - .setMaxCompletions(100); + harness = new LanguageServerHarness<>(() -> { + ConcourseLanguageServer s = new ConcourseLanguageServer(OPTIONS, github); + s.setMaxCompletions(100); + return s; }, LanguageId.CONCOURSE_PIPELINE ); @@ -4611,6 +4609,40 @@ public class ConcourseEditorTest { } } + @Test public void emptyInputPathWarning() throws Exception { + Editor editor = harness.newEditor(LanguageId.CONCOURSE_TASK, + "platform: linux\n" + + "run:\n" + + " path: blah\n" + + "inputs:\n" + + "- name: foo\n" + + " path: \"\"\n" + + "outputs:\n" + + "- name: bar\n" + + " path: \"\"\n" + ); + List problems = editor.assertProblems( + "\"\"|Empty optional String attribute is useless and can be omitted", + "\"\"|Empty optional String attribute is useless and can be omitted" + ); + for (Diagnostic problem : problems) { + assertEquals(DiagnosticSeverity.Warning, problem.getSeverity()); + } + } + + @Test public void taskInputOptionalAttribute() throws Exception { + Editor editor = harness.newEditor(LanguageId.CONCOURSE_TASK, + "platform: linux\n" + + "run:\n" + + " path: blah\n" + + "inputs:\n" + + "- name: foo\n" + + " optional: non-bool\n" + ); + editor.assertProblems("non-bool|boolean"); + editor.assertHoverContains("optional", "If `true`, then the input is not required by the task"); + } + ////////////////////////////////////////////////////////////////////////////// private void assertContextualCompletions(String conText, String textBefore, String... textAfter) throws Exception {