From 3d4c72318c047dfb03faa7000ad5a7270476ea62 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Mon, 6 Apr 2020 17:18:00 -0700 Subject: [PATCH] Quick Fix for unknown properties in application.yml See https://github.com/spring-projects/sts4/issues/442 --- .../languageserver/testharness/Editor.java | 14 ++++++ .../ApplicationYamlASTReconciler.java | 26 +++++++++-- .../boot/test/ApplicationYamlEditorTest.java | 44 +++++++++++++++++++ 3 files changed, 81 insertions(+), 3 deletions(-) diff --git a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java index 696ea2339..6ddb47794 100644 --- a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java +++ b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java @@ -813,6 +813,19 @@ public class Editor { assertEquals(expectLabel, ca.getLabel()); return ca; } + + public void assertQuickfixes(Diagnostic problem, String... expectedLabels) throws Exception { + List actions = getCodeActions(problem); + StringBuilder expecteds = new StringBuilder(); + for (String l : expectedLabels) { + expecteds.append(l+"\n"); + } + StringBuilder actuals = new StringBuilder(); + for (CodeAction a : actions) { + actuals.append(a.getLabel()+"\n"); + } + assertEquals(expecteds.toString(), actuals.toString()); + } public void assertText(String expected) { assertEquals(expected, getText()); @@ -966,4 +979,5 @@ public class Editor { this.selectionStart = this.selectionEnd = doc.toOffset(position); } + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlASTReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlASTReconciler.java index c07cfc8ec..65c932c3c 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlASTReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlASTReconciler.java @@ -17,6 +17,7 @@ import static org.springframework.ide.vscode.boot.yaml.reconcile.ApplicationYaml import static org.springframework.ide.vscode.commons.yaml.ast.NodeUtil.asScalar; import static org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST.getChildren; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -55,6 +56,7 @@ import org.springframework.ide.vscode.commons.yaml.ast.NodeRef.Kind; import org.springframework.ide.vscode.commons.yaml.ast.NodeRef.TupleValueRef; 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.reconcile.YamlASTReconciler; import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; @@ -353,9 +355,11 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { if (fixType != null) { switch (fixType.getId()) { case CommonQuickfixes.MISSING_PROPERTY_APP_QF_ID: - p.addQuickfix(new QuickfixData<>(fixType, - new MissingPropertyData(new TextDocumentIdentifier(docUri), name), - "Create metadata for `" + name +"`")); + for (String missingProp : getUnknownProperties(name, entry.getValueNode(), new ArrayList<>())) { + p.addQuickfix(new QuickfixData<>(fixType, + new MissingPropertyData(new TextDocumentIdentifier(docUri), name), + "Create metadata for `" + missingProp +"`")); + } break; } } @@ -364,6 +368,22 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { problems.accept(p); } + private List getUnknownProperties(String name, Node valueNode, List unknownProps) { + if (valueNode instanceof MappingNode) { + MappingNode map = (MappingNode) valueNode; + for (NodeTuple entry : map.getValue()) { + String key = NodeUtil.asScalar(entry.getKeyNode()); + if (key!=null) { + key = StringUtil.camelCaseToHyphens(key); + getUnknownProperties(name+"."+key, entry.getValueNode(), unknownProps); + } + } + } else { + unknownProps.add(name); + } + return unknownProps; + } + private String extendForQuickfix(String name, Node node) { if (node!=null) { TupleValueRef child = getFirstTupleValue(getChildren(node)); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java index 3a00ab5e3..876c9a2fa 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java @@ -3020,6 +3020,50 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest { assertCompletionDetailsWithDeprecation("foo:\n nam<*>", "alt-name", "String", null, Boolean.TRUE); } + @Test public void missingPropertyQuickfix() throws Exception { + IJavaProject p = createPredefinedMavenProject("empty-boot-2.1.0-app"); + useProject(p); + + { + Editor editor = newEditor( + "myapp:\n" + + " orders:\n" + + " pages: 10" + ); + Diagnostic problem = editor.assertProblems("myapp|Unknown").get(0); + editor.assertQuickfixes(problem, "Create metadata for `myapp.orders.pages`"); + } + + { + Editor editor = newEditor( + "myapp:\n" + + " orders:\n" + + " pageSize: 10" + ); + Diagnostic problem = editor.assertProblems("myapp|Unknown").get(0); + editor.assertQuickfixes(problem, "Create metadata for `myapp.orders.page-size`"); + } + + { + Editor editor = newEditor( + "myapp:\n" + + " orders:\n" + + " pageSize: 10\n" + + " start: 0\n" + ); + Diagnostic problem = editor.assertProblems("myapp|Unknown").get(0); + editor.assertQuickfixes(problem, + "Create metadata for `myapp.orders.page-size`", + "Create metadata for `myapp.orders.start`" + ); + } + + //TODO: a test case that verifies codeAction.perform has expected result. + // Carefull though, if you do add this you must make sure to cleanup any side-effects the quickfix does + // to files in the project. + + } + @Test public void testDeprecatedPropertyQuickfixSimple() throws Exception { //A simple case for starters. The path edits aren't too complicated since there's //just the one property in the file and only the last part of the 'path' changes.