From 7ce8ea1a0efee11535dbaf82e9c13e9514fd6743 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Mon, 24 Apr 2017 15:09:13 -0700 Subject: [PATCH] Quickfix for health-check-type: none --- .../boot/test/ApplicationYamlEditorTest.java | 62 ++++++++------- .../reconcile/ReconcileException.java | 17 ++++ .../reconcile/ReplacementQuickfix.java | 32 ++++++++ .../languageserver/util/DocumentRegion.java | 5 ++ .../vscode/commons/util/text/IDocument.java | 2 + .../commons/util/text/TextDocument.java | 2 +- .../YamlQuickfixes.java | 23 +++++- .../yaml/reconcile/ReplaceStringData.java | 46 +++++++++++ .../SchemaBasedYamlASTReconciler.java | 77 +++++++++++-------- .../YamlSchemaBasedReconcileEngine.java | 1 + .../yaml/reconcile/YamlSchemaProblems.java | 1 + .../commons/yaml/schema/YTypeFactory.java | 44 ++++++++--- .../concourse/ConcourseLanguageServer.java | 2 +- .../yaml/ManifestYamlLanguageServer.java | 2 +- .../manifest/yaml/ManifestYmlSchema.java | 2 +- .../manifest/yaml/ManifestYamlEditorTest.java | 27 +++++-- 16 files changed, 259 insertions(+), 86 deletions(-) create mode 100644 headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/reconcile/ReplacementQuickfix.java rename headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/{reconcile => quickfix}/YamlQuickfixes.java (79%) create mode 100644 headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/ReplaceStringData.java diff --git a/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java b/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java index f42afb166..c62c815bd 100644 --- a/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java +++ b/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java @@ -32,15 +32,15 @@ import org.springframework.ide.vscode.languageserver.testharness.Editor; /** * This class is a placeholder where we will attempt to copy and port - * as many tests a possible from + * as many tests a possible from * org.springframework.ide.eclipse.boot.properties.editor.test.YamlEditorTests - * + * * @author Kris De Volder */ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest { //////////////////////////////////////////////////////////////////////////////////////// - + @Test public void linterRunsOnDocumentOpenAndChange() throws Exception { Editor editor = newEditor( "somemap: val\n"+ @@ -50,7 +50,7 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest { editor.assertProblems( "-|expected " ); - + editor.setText( "- sequence\n" + "zomemap: val" @@ -60,9 +60,9 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest { "z|expected " ); } - + ///////////////////// ported tests from old STS code base //////////////////////////////////////////////// - + @Test public void testHovers() throws Exception { defaultTestData(); Editor editor = newEditor( @@ -212,7 +212,7 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest { ); editor.assertHoverContains("data", "Pojo"); // description from json metadata - + // NOTE: This may be failing because javadoc may not be obtained from a private member editor.assertHoverContains("wavelen", "JavaDoc from field"); // javadoc from field editor.assertHoverContains("name", "Set the name"); // javadoc from setter @@ -224,7 +224,6 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest { } @Ignore @Test public void testHyperlinkTargets() throws Exception { - System.out.println(">>> testHyperlinkTargets"); IJavaProject p = createPredefinedMavenProject("tricky-getters-boot-1.3.1-app"); useProject(p); @@ -248,7 +247,6 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest { ); editor.assertLinkTargets("init-sql", "org.springframework.boot.autoconfigure.flyway.FlywayProperties.setInitSqls(List)"); - System.out.println("<<< testHyperlinkTargets"); } @Test public void testReconcile() throws Exception { @@ -679,7 +677,7 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest { "bad: BLUE|Expecting a 'demo.Color", "Bogus|Expecting a 'demo.Color" ); - + /* * TODO: if enums are not sorted by the 3rd party java indexing lib then * perform the commented out test rather than the above @@ -2360,11 +2358,11 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest { */ @Test public void testDeprecatedReconcileProperty() throws Exception { data("error.path", "java.lang.String", null, "Path of the error controller."); - + String contents = "# a comment\n"+ "error:\n"+ " path: foo\n"; - + Editor editor = newEditor( contents ); @@ -3156,7 +3154,7 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest { } /** - * TODO: Delete this tempotary test. + * TODO: Delete this tempotary test. * This test is just one piece copied from another test to do more focussed debugging. If you find this test * in the git repo, then it was committed by accident. So feel free to delete it. */ @@ -3167,7 +3165,7 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest { data("my.nice.resource", "org.springframework.core.io.Resource", null, "A very nice resource."); data("my.nice.list", "java.util.List", null, "A nice list of resources."); - + assertCompletionWithLabel( "my:\n" + " nice:\n" + @@ -3298,24 +3296,24 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest { " classpath:stuff/wordlist.txt<*>\n" ); } - + @Test public void testCompletionsInContextWithDuplicateKey() throws Exception { //See: https://www.pivotaltracker.com/story/show/135708013 defaultTestData(); - + assertCompletions( - "spring:\n" + - " application:\n" + - " name: my-app\n" + - "spring:\n" + - " activemq:\n" + + "spring:\n" + + " application:\n" + + " name: my-app\n" + + "spring:\n" + + " activemq:\n" + " broker-u<*>" , // ==> - "spring:\n" + - " application:\n" + - " name: my-app\n" + - "spring:\n" + - " activemq:\n" + + "spring:\n" + + " application:\n" + + " name: my-app\n" + + "spring:\n" + + " activemq:\n" + " broker-url: <*>" ); } @@ -3596,7 +3594,7 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest { @Test public void PT_137299017_extra_space_wth_bean_property_value_completion() throws Exception { useProject(createPredefinedMavenProject("enums-boot-1.3.2-app")); data("color", "demo.ColorData", null, "colorful stuff"); - + assertCompletions( "color:\n" + " next:<*>" @@ -3615,17 +3613,17 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest { @Test public void PT_137299017_extra_space_wth_index_property_value_completion() throws Exception { defaultTestData(); assertCompletions( - "flyway:\n"+ + "flyway:\n"+ " enabled:<*>" , // ==> - "flyway:\n"+ + "flyway:\n"+ " enabled: false<*>" , // == - "flyway:\n"+ + "flyway:\n"+ " enabled: true<*>" ); } - + ///////////////// cruft //////////////////////////////////////////////////////// private void generateNestedProperties(int levels, String[] names, String prefix) { @@ -3656,5 +3654,5 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest { protected String getFileExtension() { return ".yml"; } - + } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/reconcile/ReconcileException.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/reconcile/ReconcileException.java index 701cee6bd..07ff4b021 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/reconcile/ReconcileException.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/reconcile/ReconcileException.java @@ -10,6 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.languageserver.reconcile; +import org.springframework.ide.vscode.commons.util.Assert; +import org.springframework.ide.vscode.commons.util.StringUtil; import org.springframework.ide.vscode.commons.util.ValueParseException; /** @@ -23,6 +25,8 @@ public class ReconcileException extends ValueParseException implements ProblemTy private static final long serialVersionUID = 1L; private final ProblemType problemType; + private ReplacementQuickfix replacement = null; //Optional info to create a 'replacement quickfix' for the value + public ReconcileException(String message, ProblemType problemType) { super(message); this.problemType = problemType; @@ -37,4 +41,17 @@ public class ReconcileException extends ValueParseException implements ProblemTy public ProblemType getProblemType() { return problemType; } + + public ReconcileException fixWith(ReplacementQuickfix replacement) { + //Silently ignore if the fix is either null or doesn't provide a proper replacement text. + if (replacement!=null && StringUtil.hasText(replacement.replacement)) { + Assert.isLegal(this.replacement==null, "Multiple fixes not yet supported"); + this.replacement = replacement; + } + return this; + } + + public ReplacementQuickfix getReplacement() { + return replacement; + } } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/reconcile/ReplacementQuickfix.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/reconcile/ReplacementQuickfix.java new file mode 100644 index 000000000..220a32aa5 --- /dev/null +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/reconcile/ReplacementQuickfix.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2017 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.languageserver.reconcile; + +/** + * An info object that can be attached to a {@link ReconcileException} to indicate that + * the problem can be fixed by a simple value substitution at the exact location of + * the problem marker. + * + * @author Kris De Volder + */ +public class ReplacementQuickfix { + public final String msg; + public final String replacement; + public ReplacementQuickfix(String msg, String replacement) { + super(); + this.msg = msg; + this.replacement = replacement; + } + @Override + public String toString() { + return "ReplacementQuickfix [msg=" + msg + ", replacement=" + replacement + "]"; + } +} \ No newline at end of file diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/DocumentRegion.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/DocumentRegion.java index b44f00a1d..7cf43f095 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/DocumentRegion.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/DocumentRegion.java @@ -16,6 +16,7 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.eclipse.lsp4j.Range; import org.springframework.ide.vscode.commons.util.Assert; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.text.IDocument; @@ -290,4 +291,8 @@ public class DocumentRegion implements CharSequence { numChars = Math.min(getLength(), numChars); return new DocumentRegion(doc, end-numChars, end); } + + public Range asRange() throws BadLocationException { + return doc.toRange(asRegion()); + } } \ No newline at end of file diff --git a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/IDocument.java b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/IDocument.java index e2e3041c1..bfa565644 100644 --- a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/IDocument.java +++ b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/IDocument.java @@ -11,6 +11,7 @@ package org.springframework.ide.vscode.commons.util.text; +import org.eclipse.lsp4j.Range; import org.springframework.ide.vscode.commons.util.BadLocationException; public interface IDocument { @@ -30,5 +31,6 @@ public interface IDocument { String textBetween(int start, int end) throws BadLocationException; LanguageId getLanguageId(); int getVersion(); + Range toRange(IRegion asRegion) throws BadLocationException; } diff --git a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/TextDocument.java b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/TextDocument.java index 771f059e0..ec79fe6b4 100644 --- a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/TextDocument.java +++ b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/TextDocument.java @@ -11,7 +11,6 @@ package org.springframework.ide.vscode.commons.util.text; -import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -278,6 +277,7 @@ public class TextDocument implements IDocument { return languageId; } + @Override public Range toRange(IRegion region) throws BadLocationException { return toRange(region.getOffset(), region.getLength()); } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlQuickfixes.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/quickfix/YamlQuickfixes.java similarity index 79% rename from headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlQuickfixes.java rename to headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/quickfix/YamlQuickfixes.java index a4b88a170..b4d15c6fc 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlQuickfixes.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/quickfix/YamlQuickfixes.java @@ -8,7 +8,7 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.commons.yaml.reconcile; +package org.springframework.ide.vscode.commons.yaml.quickfix; import org.eclipse.lsp4j.TextEdit; import org.eclipse.lsp4j.WorkspaceEdit; @@ -21,6 +21,8 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument; import org.springframework.ide.vscode.commons.yaml.completion.YamlPathEdits; 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.MissingPropertiesData; +import org.springframework.ide.vscode.commons.yaml.reconcile.ReplaceStringData; import org.springframework.ide.vscode.commons.yaml.structure.YamlDocument; import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SChildBearingNode; import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SNode; @@ -33,6 +35,7 @@ import com.google.common.collect.ImmutableMap; public class YamlQuickfixes { public final QuickfixType MISSING_PROP_FIX; + public final QuickfixType SIMPLE_TEXT_EDIT; public YamlQuickfixes(QuickfixRegistry r, SimpleTextDocumentService textDocumentService, YamlStructureProvider structureProvider) { MISSING_PROP_FIX = r.register("MISSING_PROP_FIX", (Object _params) -> { @@ -69,6 +72,24 @@ public class YamlQuickfixes { //Something went wrong. Return empty edit object. return new WorkspaceEdit(ImmutableMap.of(), null); }); + + SIMPLE_TEXT_EDIT = r.register("SIMPLE_TEXT_EDIT", (_params) -> { + try { + ReplaceStringData params = new ObjectMapper().convertValue(_params, ReplaceStringData.class); + TextDocument _doc = textDocumentService.getDocument(params.getUri()); + if (_doc!=null) { + return new WorkspaceEdit( + ImmutableMap.of(params.getUri(), ImmutableList.of(params.getEdit())), + null + ); + } + } catch (Exception e) { + Log.log(e); + } + //Something went wrong. Return empty edit object. + //Something went wrong. Return empty edit object. + return new WorkspaceEdit(ImmutableMap.of(), null); + }); } } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/ReplaceStringData.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/ReplaceStringData.java new file mode 100644 index 000000000..da2c1cccc --- /dev/null +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/ReplaceStringData.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2017 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.eclipse.lsp4j.TextEdit; +import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; +import org.springframework.ide.vscode.commons.util.BadLocationException; + +public class ReplaceStringData { + + private String uri; + private TextEdit edit; + + public ReplaceStringData() {} + + public ReplaceStringData(DocumentRegion target, String newText) throws BadLocationException { + this.uri = target.getDocument().getUri(); + this.edit = new TextEdit(target.asRange(), newText); + } + + public TextEdit getEdit() { + return edit; + } + public void setEdit(TextEdit edit) { + this.edit = edit; + } + + public String getUri() { + return uri; + } + public void setUri(String uri) { + this.uri = uri; + } + @Override + public String toString() { + return "ReplaceStringData [uri=" + uri + ", edit=" + edit + "]"; + } +} 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 0e4171994..7097c365b 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 @@ -23,9 +23,13 @@ import java.util.TreeSet; import java.util.regex.Pattern; import java.util.stream.Collectors; +import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix.QuickfixData; import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType; import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemTypeProvider; +import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileException; +import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl; +import org.springframework.ide.vscode.commons.languageserver.reconcile.ReplacementQuickfix; import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; import org.springframework.ide.vscode.commons.util.ExceptionUtil; import org.springframework.ide.vscode.commons.util.IntegerRange; @@ -38,9 +42,9 @@ 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.path.YamlPath; 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; @@ -181,21 +185,9 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler { } } catch (Exception e) { ProblemType problemType = getProblemType(e); - DocumentRegion region = new DocumentRegion(doc, node.getStartMark().getIndex(), node.getEndMark().getIndex()); - if (e instanceof ValueParseException) { - ValueParseException parseException = (ValueParseException) e; - int start = parseException.getStartIndex() >= 0 - ? Math.min(node.getStartMark().getIndex() + parseException.getStartIndex(), - node.getEndMark().getIndex()) - : node.getStartMark().getIndex(); - int end = parseException.getEndIndex() >= 0 - ? Math.min(node.getStartMark().getIndex() + parseException.getEndIndex(), - node.getEndMark().getIndex()) - : node.getEndMark().getIndex(); - region = new DocumentRegion(doc, start, end); - } + DocumentRegion region = getRegion(e, doc, node); String msg = getMessage(e); - valueParseError(type, region, msg, problemType); + valueParseError(type, region, msg, problemType, getValueReplacement(e)); } } } else { @@ -208,6 +200,31 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler { } } + protected ReplacementQuickfix getValueReplacement(Exception _e) { + if (_e instanceof ReconcileException) { + ReconcileException e = (ReconcileException) _e; + return e.getReplacement(); + } + return null; + } + + protected DocumentRegion getRegion(Exception e, IDocument doc, Node node) { + DocumentRegion region = new DocumentRegion(doc, node.getStartMark().getIndex(), node.getEndMark().getIndex()); + if (e instanceof ValueParseException) { + ValueParseException parseException = (ValueParseException) e; + int start = parseException.getStartIndex() >= 0 + ? Math.min(node.getStartMark().getIndex() + parseException.getStartIndex(), + node.getEndMark().getIndex()) + : node.getStartMark().getIndex(); + int end = parseException.getEndIndex() >= 0 + ? Math.min(node.getStartMark().getIndex() + parseException.getEndIndex(), + node.getEndMark().getIndex()) + : node.getEndMark().getIndex(); + region = new DocumentRegion(doc, start, end); + } + return region; + } + private String getMessage(Exception _e) { Throwable e = ExceptionUtil.getDeepestCause(_e); @@ -337,18 +354,24 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler { } } - private void valueParseError(YType type, Node node, String parseErrorMsg, ProblemType problemType) { + private void valueParseError(YType type, DocumentRegion region, String parseErrorMsg, ProblemType problemType, ReplacementQuickfix fix) { if (!StringUtil.hasText(parseErrorMsg)) { parseErrorMsg= "Couldn't parse as '"+describe(type)+"'"; } - problem(node, parseErrorMsg, problemType); - } - - private void valueParseError(YType type, DocumentRegion region, String parseErrorMsg, ProblemType problemType) { - if (!StringUtil.hasText(parseErrorMsg)) { - parseErrorMsg= "Couldn't parse as '"+describe(type)+"'"; + ReconcileProblemImpl problem = YamlSchemaProblems.problem(problemType, parseErrorMsg, region); + if (fix!=null && StringUtil.hasText(fix.replacement)) { + try { + problem.addQuickfix( + new QuickfixData<>(quickfixes.SIMPLE_TEXT_EDIT, + new ReplaceStringData(region, fix.replacement), + fix.msg + ) + ); + } catch (Exception e) { + Log.log(e); + } } - problem(region, parseErrorMsg, problemType); + problems.accept(problem); } private void unknownBeanProperty(Node keyNode, YType type, String name) { @@ -404,14 +427,6 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler { problems.accept(YamlSchemaProblems.schemaProblem(msg, node)); } - private void problem(Node node, String msg, ProblemType problemType) { - problems.accept(YamlSchemaProblems.problem(problemType, msg, node)); - } - - private void problem(DocumentRegion region, String msg, ProblemType problemType) { - problems.accept(YamlSchemaProblems.problem(problemType, msg, region)); - } - private void problem(DocumentRegion region, String msg) { problems.accept(YamlSchemaProblems.schemaProblem(msg, region)); } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlSchemaBasedReconcileEngine.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlSchemaBasedReconcileEngine.java index eb6d18cc0..32a96712b 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlSchemaBasedReconcileEngine.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlSchemaBasedReconcileEngine.java @@ -15,6 +15,7 @@ import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemC import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem; import org.springframework.ide.vscode.commons.util.text.IDocument; import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider; +import org.springframework.ide.vscode.commons.yaml.quickfix.YamlQuickfixes; import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema; /** 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 7298dc039..e97ab8fde 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 @@ -49,6 +49,7 @@ public class YamlSchemaProblems { public static final ProblemType SYNTAX_PROBLEM = problemType("YamlSyntaxProblem"); public static final ProblemType SCHEMA_PROBLEM = problemType("YamlSchemaProblem"); public static final ProblemType DEPRECATED_PROPERTY = problemType("DeprecatedProperty", ProblemSeverity.WARNING); + 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); 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 57b8a3cf5..a33d7da92 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 @@ -26,14 +26,13 @@ import java.util.function.BiFunction; import java.util.stream.Collectors; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileException; +import org.springframework.ide.vscode.commons.languageserver.reconcile.ReplacementQuickfix; import org.springframework.ide.vscode.commons.util.Assert; import org.springframework.ide.vscode.commons.util.EnumValueParser; 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.ValueParser; import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems; -import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.AbstractType; import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraint; import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraints; @@ -53,12 +52,25 @@ import reactor.core.publisher.Flux; */ public class YTypeFactory { + private static class Deprecation { + final String errorMsg; + final String replacement; + final String quickfixMsg; + public Deprecation(String errorMsg, String replacement, String quickfixMsg) { + super(); + this.errorMsg = errorMsg; + this.replacement = replacement; + this.quickfixMsg = quickfixMsg; + } + + } + public class EnumTypeBuilder { private String name; private String[] values; - private Map deprecationMsgs = new HashMap<>(); + private Map deprecations = new HashMap<>(); public EnumTypeBuilder(String name, String[] values) { this.name = name; @@ -69,17 +81,15 @@ public class YTypeFactory { EnumValueParser basicParser = new EnumValueParser(name, values); YAtomicType t = yatomic(name); t.addHints(getNonDeprecatedValues()); - if (deprecationMsgs.isEmpty()) { + if (deprecations.isEmpty()) { t.parseWith(basicParser); } else { t.parseWith(ValueParser.of((String value) -> { basicParser.parse(value); - if (deprecationMsgs.containsKey(value)) { - String msg = deprecationMsgs.get(value); - if (!StringUtil.hasText(msg)) { - msg = "'"+value+"' is deprecated"; - } - throw new ReconcileException(msg, YamlSchemaProblems.DEPRECATED_PROPERTY); + Deprecation d = deprecations.get(value); + if (d!=null) { + throw new ReconcileException(d.errorMsg, YamlSchemaProblems.DEPRECATED_VALUE) + .fixWith(new ReplacementQuickfix(d.quickfixMsg, d.replacement)); } return value; })); @@ -89,13 +99,23 @@ public class YTypeFactory { public EnumTypeBuilder deprecate(String value, String msg) { Assert.isLegal(ImmutableSet.copyOf(values).contains(value)); - deprecationMsgs.put(value, msg); + deprecations.put(value, new Deprecation(msg, null, null)); + return this; + } + + public EnumTypeBuilder deprecateWithReplacement(String value, String replacement) { + Assert.isLegal(ImmutableSet.copyOf(values).contains(value)); + deprecations.put(value, new Deprecation( + "The value '"+value+"' is deprecated in favor of '"+replacement+"'", + replacement, + "Replace deprecated value '"+value+"' by '"+replacement+"'" + )); return this; } private String[] getNonDeprecatedValues() { return Flux.fromArray(values) - .filter((value) -> !deprecationMsgs.containsKey(value)) + .filter((value) -> !deprecations.containsKey(value)) .collectList() .map(l -> l.toArray(new String[l.size()])) .block(); diff --git a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseLanguageServer.java b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseLanguageServer.java index 1b04a60ba..ad0796a36 100644 --- a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseLanguageServer.java +++ b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseLanguageServer.java @@ -31,7 +31,7 @@ import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider; import org.springframework.ide.vscode.commons.yaml.completion.SchemaBasedYamlAssistContextProvider; import org.springframework.ide.vscode.commons.yaml.completion.YamlCompletionEngine; import org.springframework.ide.vscode.commons.yaml.hover.YamlHoverInfoProvider; -import org.springframework.ide.vscode.commons.yaml.reconcile.YamlQuickfixes; +import org.springframework.ide.vscode.commons.yaml.quickfix.YamlQuickfixes; import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaBasedReconcileEngine; import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems; import org.springframework.ide.vscode.commons.yaml.schema.YType; diff --git a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServer.java b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServer.java index 3f930de13..a149f00bd 100644 --- a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServer.java +++ b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServer.java @@ -34,7 +34,7 @@ import org.springframework.ide.vscode.commons.yaml.completion.SchemaBasedYamlAss import org.springframework.ide.vscode.commons.yaml.completion.YamlAssistContextProvider; import org.springframework.ide.vscode.commons.yaml.completion.YamlCompletionEngine; import org.springframework.ide.vscode.commons.yaml.hover.YamlHoverInfoProvider; -import org.springframework.ide.vscode.commons.yaml.reconcile.YamlQuickfixes; +import org.springframework.ide.vscode.commons.yaml.quickfix.YamlQuickfixes; import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaBasedReconcileEngine; import org.springframework.ide.vscode.commons.yaml.schema.YValueHint; import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema; diff --git a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java index bbe109ffb..4bb908d37 100644 --- a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java +++ b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java @@ -108,7 +108,7 @@ public class ManifestYmlSchema implements YamlSchema { t_memory.parseWith(ManifestYmlValueParsers.MEMORY); YAtomicType t_health_check_type = f.yenumBuilder("Health Check Type", "none", "process", "port", "http") - .deprecate("none", "The value 'none' is deprecated in favor of 'process'") + .deprecateWithReplacement("none", "process") .build(); YAtomicType t_strictly_pos_integer = f.yatomic("Strictly Positive Integer"); diff --git a/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java b/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java index 48af82f0e..10dbaf28a 100644 --- a/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java +++ b/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java @@ -29,6 +29,7 @@ import org.springframework.ide.vscode.commons.cloudfoundry.client.CFDomain; import org.springframework.ide.vscode.commons.cloudfoundry.client.CFServiceInstance; import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientRequests; import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.NoTargetsException; +import org.springframework.ide.vscode.languageserver.testharness.CodeAction; import org.springframework.ide.vscode.languageserver.testharness.Editor; import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness; @@ -440,12 +441,6 @@ public class ManifestYamlEditorTest { problem = editor.assertProblems("none|'none' is deprecated in favor of 'process'").get(0); assertEquals(DiagnosticSeverity.Warning, problem.getSeverity()); - editor = harness.newEditor( - "applications:\n" + - "- name: foo\n" + - " health-check-type: none" - ); - editor = harness.newEditor( "applications:\n" + "- name: foo\n" + @@ -461,6 +456,25 @@ public class ManifestYamlEditorTest { editor.assertProblems(/*NONE*/); } + @Test public void deprecatedHealthCheckTypeQuickfix() throws Exception { + Editor editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " health-check-type: none" + ); + Diagnostic problem = editor.assertProblems("none|'none' is deprecated in favor of 'process'").get(0); + assertEquals(DiagnosticSeverity.Warning, problem.getSeverity()); + CodeAction quickfix = editor.assertCodeAction(problem); + assertEquals("Replace deprecated value 'none' by 'process'", quickfix.getLabel()); + quickfix.perform(); + + editor.assertRawText( + "applications:\n" + + "- name: foo\n" + + " health-check-type: process" + ); + } + @Test public void hoverInfos() throws Exception { Editor editor = harness.newEditor( @@ -1147,6 +1161,7 @@ public class ManifestYamlEditorTest { editor.assertProblems(); } + ////////////////////////////////////////////////////////////////////////////// private void assertCompletions(String textBefore, String... textAfter) throws Exception {