From dd554ce36908943ff5aaf11e07be8d4522d95d9b Mon Sep 17 00:00:00 2001 From: Minesh Patel Date: Tue, 17 Dec 2024 11:17:00 +0000 Subject: [PATCH 1/3] Added Quick fix for the constructor syntax --- .../ide/quickfix/ConstructorQuickFix.java | 93 +++++++++++++++ .../ide/quickfix/RosettaQuickFixProvider.java | 111 ++++++++++-------- .../ide/quickfix/ConstructorQuickFixTest.java | 103 ++++++++++++++++ .../rosetta/validation/RosettaIssueCodes.java | 1 + .../validation/RosettaSimpleValidator.xtend | 6 +- 5 files changed, 262 insertions(+), 52 deletions(-) create mode 100644 rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/ConstructorQuickFix.java create mode 100644 rosetta-ide/src/test/java/com/regnosys/rosetta/ide/quickfix/ConstructorQuickFixTest.java diff --git a/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/ConstructorQuickFix.java b/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/ConstructorQuickFix.java new file mode 100644 index 000000000..16af32e2d --- /dev/null +++ b/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/ConstructorQuickFix.java @@ -0,0 +1,93 @@ +package com.regnosys.rosetta.ide.quickfix; + +import com.google.common.collect.Lists; +import com.regnosys.rosetta.RosettaEcoreUtil; +import com.regnosys.rosetta.rosetta.RosettaFeature; +import com.regnosys.rosetta.rosetta.expression.ConstructorKeyValuePair; +import com.regnosys.rosetta.rosetta.expression.ExpressionFactory; +import com.regnosys.rosetta.rosetta.expression.RosettaConstructorExpression; +import com.regnosys.rosetta.rosetta.simple.Attribute; +import com.regnosys.rosetta.rosetta.simple.ChoiceOption; +import com.regnosys.rosetta.types.RMetaAnnotatedType; +import com.regnosys.rosetta.types.RosettaTypeProvider; + +import javax.inject.Inject; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static java.util.function.Predicate.not; + +public class ConstructorQuickFix { + @Inject + private RosettaTypeProvider types; + @Inject + private RosettaEcoreUtil extensions; + + public void modifyConstructorWithMandatoryAttributes(RosettaConstructorExpression constructor) { + RosettaFeatureGroup rosettaFeatureGroup = groupConstructorFeatures(constructor); + List requiredAbsentAttributes = rosettaFeatureGroup.requiredAbsentAttributes(); + List optionalAbsentAttributes = rosettaFeatureGroup.optionalAbsentAttributes(); + + requiredAbsentAttributes.forEach(attr -> { + ConstructorKeyValuePair constructorKeyValuePair = ExpressionFactory.eINSTANCE.createConstructorKeyValuePair(); + constructorKeyValuePair.setKey(attr); + constructorKeyValuePair.setValue(ExpressionFactory.eINSTANCE.createListLiteral()); + constructor.getValues().add(constructorKeyValuePair); + }); + if (!optionalAbsentAttributes.isEmpty()) { + constructor.setImplicitEmpty(true); + } + } + + private RosettaFeatureGroup groupConstructorFeatures(RosettaConstructorExpression constructor) { + if (constructor != null) { + RMetaAnnotatedType metaAnnotatedType = types.getRMetaAnnotatedType(constructor); + if (metaAnnotatedType != null && metaAnnotatedType.getRType() != null) { + List populatedFeatures = populatedFeaturesInConstructor(constructor); + List allFeatures = Lists.newArrayList(extensions.allFeatures(metaAnnotatedType.getRType(), constructor)); + return new RosettaFeatureGroup(populatedFeatures, allFeatures); + } + } + return new RosettaFeatureGroup(); + } + + private List populatedFeaturesInConstructor(RosettaConstructorExpression constructor) { + return constructor.getValues().stream() + .map(ConstructorKeyValuePair::getKey) + .collect(Collectors.toList()); + } + + private static class RosettaFeatureGroup { + private final List populated; + private final List all; + + private RosettaFeatureGroup() { + this.populated = Collections.emptyList(); + this.all = Collections.emptyList(); + } + + private RosettaFeatureGroup(List populated, List all) { + this.populated = populated; + this.all = all; + } + + private List requiredAbsentAttributes() { + return all.stream() + .filter(x -> !populated.contains(x)) + .filter(RosettaFeatureGroup::isRequired) + .collect(Collectors.toList()); + } + + private List optionalAbsentAttributes() { + return all.stream() + .filter(x -> !populated.contains(x)) + .filter(not(RosettaFeatureGroup::isRequired)) + .collect(Collectors.toList()); + } + + private static boolean isRequired(RosettaFeature it) { + return !(it instanceof Attribute) || ((Attribute) it).getCard().getInf() != 0 || (it instanceof ChoiceOption); + } + } +} diff --git a/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/RosettaQuickFixProvider.java b/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/RosettaQuickFixProvider.java index 78d6dd70f..9fc433b41 100644 --- a/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/RosettaQuickFixProvider.java +++ b/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/RosettaQuickFixProvider.java @@ -16,68 +16,79 @@ package com.regnosys.rosetta.ide.quickfix; -import java.util.List; +import java.util.*; import javax.inject.Inject; +import com.regnosys.rosetta.rosetta.expression.*; +import com.regnosys.rosetta.types.RosettaTypeProvider; import org.eclipse.emf.ecore.EObject; import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.TextEdit; -import org.eclipse.xtext.ide.editor.quickfix.AbstractDeclarativeIdeQuickfixProvider; -import org.eclipse.xtext.ide.editor.quickfix.DiagnosticResolutionAcceptor; -import org.eclipse.xtext.ide.editor.quickfix.QuickFix; +import org.eclipse.xtext.ide.editor.quickfix.*; import org.eclipse.xtext.ide.server.Document; import com.regnosys.rosetta.ide.util.RangeUtils; import com.regnosys.rosetta.validation.RosettaIssueCodes; -import com.regnosys.rosetta.rosetta.expression.RosettaUnaryOperation; + import static com.regnosys.rosetta.rosetta.expression.ExpressionPackage.Literals.*; +import static org.eclipse.xtext.EcoreUtil2.getContainerOfType; public class RosettaQuickFixProvider extends AbstractDeclarativeIdeQuickfixProvider { - @Inject - private RangeUtils rangeUtils; - - @QuickFix(RosettaIssueCodes.REDUNDANT_SQUARE_BRACKETS) - public void fixRedundantSquareBrackets(DiagnosticResolutionAcceptor acceptor) { - acceptor.accept("Remove square brackets.", (Diagnostic diagnostic, EObject object, Document document) -> { - Range range = rangeUtils.getRange(object); - String original = document.getSubstring(range); - String edited = original.replaceAll("^\\[ +|\\s+\\]$", ""); - return createTextEdit(diagnostic, edited); - }); - } - - @QuickFix(RosettaIssueCodes.MANDATORY_SQUARE_BRACKETS) - public void fixMandatorySquareBrackets(DiagnosticResolutionAcceptor acceptor) { - acceptor.accept("Add square brackets.", (Diagnostic diagnostic, EObject object, Document document) -> { - Range range = rangeUtils.getRange(object); - String original = document.getSubstring(range); - String edited = "[ " + original + " ]"; - return createTextEdit(diagnostic, edited); - }); - } - - @QuickFix(RosettaIssueCodes.MANDATORY_THEN) - public void fixMandatoryThen(DiagnosticResolutionAcceptor acceptor) { - acceptor.accept("Add `then`.", (Diagnostic diagnostic, EObject object, Document document) -> { - RosettaUnaryOperation op = (RosettaUnaryOperation)object; - Range range = rangeUtils.getRange(op, ROSETTA_OPERATION__OPERATOR); - String original = document.getSubstring(range); - String edited = "then " + original; - TextEdit edit = new TextEdit(range, edited); - return List.of(edit); - }); - } - - @QuickFix(RosettaIssueCodes.DEPRECATED_MAP) - public void fixDeprecatedMap(DiagnosticResolutionAcceptor acceptor) { - acceptor.accept("Replace with `extract`.", (Diagnostic diagnostic, EObject object, Document document) -> { - RosettaUnaryOperation op = (RosettaUnaryOperation)object; - Range range = rangeUtils.getRange(op, ROSETTA_OPERATION__OPERATOR); - String edited = "extract"; - TextEdit edit = new TextEdit(range, edited); - return List.of(edit); - }); - } + @Inject + private RangeUtils rangeUtils; + @Inject + private ConstructorQuickFix constructorQuickFix; + + @QuickFix(RosettaIssueCodes.REDUNDANT_SQUARE_BRACKETS) + public void fixRedundantSquareBrackets(DiagnosticResolutionAcceptor acceptor) { + acceptor.accept("Remove square brackets.", (Diagnostic diagnostic, EObject object, Document document) -> { + Range range = rangeUtils.getRange(object); + String original = document.getSubstring(range); + String edited = original.replaceAll("^\\[ +|\\s+\\]$", ""); + return createTextEdit(diagnostic, edited); + }); + } + + @QuickFix(RosettaIssueCodes.MANDATORY_SQUARE_BRACKETS) + public void fixMandatorySquareBrackets(DiagnosticResolutionAcceptor acceptor) { + acceptor.accept("Add square brackets.", (Diagnostic diagnostic, EObject object, Document document) -> { + Range range = rangeUtils.getRange(object); + String original = document.getSubstring(range); + String edited = "[ " + original + " ]"; + return createTextEdit(diagnostic, edited); + }); + } + + @QuickFix(RosettaIssueCodes.MANDATORY_THEN) + public void fixMandatoryThen(DiagnosticResolutionAcceptor acceptor) { + acceptor.accept("Add `then`.", (Diagnostic diagnostic, EObject object, Document document) -> { + RosettaUnaryOperation op = (RosettaUnaryOperation) object; + Range range = rangeUtils.getRange(op, ROSETTA_OPERATION__OPERATOR); + String original = document.getSubstring(range); + String edited = "then " + original; + TextEdit edit = new TextEdit(range, edited); + return List.of(edit); + }); + } + + @QuickFix(RosettaIssueCodes.DEPRECATED_MAP) + public void fixDeprecatedMap(DiagnosticResolutionAcceptor acceptor) { + acceptor.accept("Replace with `extract`.", (Diagnostic diagnostic, EObject object, Document document) -> { + RosettaUnaryOperation op = (RosettaUnaryOperation) object; + Range range = rangeUtils.getRange(op, ROSETTA_OPERATION__OPERATOR); + String edited = "extract"; + TextEdit edit = new TextEdit(range, edited); + return List.of(edit); + }); + } + + @QuickFix(RosettaIssueCodes.MISSING_MANDATORY_CONSTRUCTOR_ARGUMENT) + public void missingAttributes(DiagnosticResolutionAcceptor acceptor) { + ISemanticModification semanticModification = (Diagnostic diagnostic, EObject object) -> + context -> constructorQuickFix.modifyConstructorWithMandatoryAttributes(getContainerOfType(object, RosettaConstructorExpression.class)); + acceptor.accept("Auto add mandatory attributes.", semanticModification); + } + } diff --git a/rosetta-ide/src/test/java/com/regnosys/rosetta/ide/quickfix/ConstructorQuickFixTest.java b/rosetta-ide/src/test/java/com/regnosys/rosetta/ide/quickfix/ConstructorQuickFixTest.java new file mode 100644 index 000000000..20c55cd29 --- /dev/null +++ b/rosetta-ide/src/test/java/com/regnosys/rosetta/ide/quickfix/ConstructorQuickFixTest.java @@ -0,0 +1,103 @@ +package com.regnosys.rosetta.ide.quickfix; + +import com.regnosys.rosetta.ide.tests.AbstractRosettaLanguageServerTest; +import com.regnosys.rosetta.ide.util.RangeUtils; +import org.eclipse.lsp4j.CodeAction; +import org.eclipse.lsp4j.Command; +import org.eclipse.lsp4j.TextEdit; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.junit.Test; + +import javax.inject.Inject; +import java.util.Collection; +import java.util.List; + +import static org.eclipse.xtext.xbase.lib.IterableExtensions.head; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.sortWith; + +public class ConstructorQuickFixTest extends AbstractRosettaLanguageServerTest { + + @Inject + RangeUtils rangeUtils; + + @Test + public void testQuickFixMandatoryAttributes() { + testCodeAction(configurator -> { + configurator.setModel(( + "namespace testQuickFixDeprecatedMap;" + + "type T:;" + + " a string (1..1);" + + "func F:;" + + " output: t T (1..1);" + + " set t : T {};" + ).replace(";", "\n")); + configurator.setAssertCodeActions(codeActions -> { + assertCodeAction(codeActions, "T { a: empty }"); + }); + }); + } + + @Test + public void testQuickFixMandatoryAndOptionalAttributes() { + testCodeAction(configurator -> { + configurator.setModel(( + "namespace testQuickFixDeprecatedMap;" + + "type T:;" + + " a string (1..1);" + + " b string (0..1);" + + "func F:;" + + " output: t T (1..1);" + + " set t : T {};" + ).replace(";", "\n")); + configurator.setAssertCodeActions(codeActions -> { + assertCodeAction(codeActions, "T { a: empty, ... }"); + }); + }); + } + + @Test + public void testQuickFixMandatoryChoiceAttributes() { + testCodeAction(configurator -> { + configurator.setModel(( + "namespace testQuickFixDeprecatedMap;" + + "type A:;" + + " n string (0..1);" + + "type B:;" + + " m string (0..1);" + + "choice C:;" + + " A;" + + " B;" + + "func F:;" + + " output: c C (1..1);" + + " set c : C {};" + ).replace(";", "\n")); + configurator.setAssertCodeActions(codeActions -> { + assertCodeAction(codeActions, "C { A: empty, B: empty }"); + }); + }); + } + + private void assertCodeAction(List> codeActions, String expectedFix) { + CodeAction codeAction = getFirstCodeAction(codeActions); + assertEquals("Auto add mandatory attributes.", codeAction.getTitle()); + TextEdit textEdit = getFirstTextEdit(codeAction); + String actual = textEdit.getNewText().replaceAll("\\s", ""); + String expected = expectedFix.replaceAll("\\s", ""); + assertEquals(expected, actual); + } + + private static TextEdit getFirstTextEdit(CodeAction codeAction) { + return codeAction.getEdit().getChanges().values() + .stream().flatMap(Collection::stream) + .findFirst().orElseThrow(); + } + + private CodeAction getFirstCodeAction(List> codeActions) { + List> sorted = + sortWith(codeActions, (a1, b1) -> + this.rangeUtils.comparePositions(head(a1.getRight().getDiagnostics()).getRange().getStart(), + head(b1.getRight().getDiagnostics()).getRange().getStart())); + + return sorted.get(0).getRight(); + } +} diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaIssueCodes.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaIssueCodes.java index ab1f6144b..d6c714337 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaIssueCodes.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaIssueCodes.java @@ -42,4 +42,5 @@ public interface RosettaIssueCodes { static final String REDUNDANT_SQUARE_BRACKETS = PREFIX + "redundantSquareBrackets"; static final String MANDATORY_THEN = PREFIX + "mandatoryThen"; static final String DEPRECATED_MAP = PREFIX + "deprecatedMap"; // @Compat + static final String MISSING_MANDATORY_CONSTRUCTOR_ARGUMENT = PREFIX + "missingAttributes"; } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaSimpleValidator.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaSimpleValidator.xtend index f60a47cc5..f51d2cb94 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaSimpleValidator.xtend +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaSimpleValidator.xtend @@ -1192,14 +1192,16 @@ class RosettaSimpleValidator extends AbstractDeclarativeRosettaValidator { .filter[!(it instanceof Attribute) || (it as Attribute).card.inf !== 0] if (ele.implicitEmpty) { if (!requiredAbsentAttributes.empty) { - error('''Missing attributes «FOR attr : requiredAbsentAttributes SEPARATOR ', '»`«attr.name»`«ENDFOR».''', ele.typeCall, null) + // Already have a ... and still missing mandatory attributes + error('''Missing attributes «FOR attr : requiredAbsentAttributes SEPARATOR ', '»`«attr.name»`«ENDFOR».''', ele, null, MISSING_MANDATORY_CONSTRUCTOR_ARGUMENT) } if (absentAttributes.size === requiredAbsentAttributes.size) { error('''There are no optional attributes left.''', ele, ROSETTA_CONSTRUCTOR_EXPRESSION__IMPLICIT_EMPTY) } } else { + // Do not have a ... and still missing mandatory/optional attributes if (!absentAttributes.empty) { - error('''Missing attributes «FOR attr : absentAttributes SEPARATOR ', '»`«attr.name»`«ENDFOR».«IF requiredAbsentAttributes.empty» Perhaps you forgot a `...` at the end of the constructor?«ENDIF»''', ele.typeCall, null) + error('''Missing attributes «FOR attr : absentAttributes SEPARATOR ', '»`«attr.name»`«ENDFOR».«IF requiredAbsentAttributes.empty» Perhaps you forgot a `...` at the end of the constructor?«ENDIF»''', ele, null, MISSING_MANDATORY_CONSTRUCTOR_ARGUMENT) } } } From a882d3e745a23844fa2ef1a71611d7211289cd4c Mon Sep 17 00:00:00 2001 From: Minesh Patel Date: Mon, 6 Jan 2025 13:06:31 +0000 Subject: [PATCH 2/3] Added commnet --- .../ide/quickfix/RosettaQuickFixCodeActionService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/RosettaQuickFixCodeActionService.java b/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/RosettaQuickFixCodeActionService.java index bdf113516..ad5777fb6 100644 --- a/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/RosettaQuickFixCodeActionService.java +++ b/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/RosettaQuickFixCodeActionService.java @@ -61,6 +61,8 @@ public List> getCodeActions(Options options) { .sorted(Comparator.nullsLast(Comparator.comparing(DiagnosticResolution::getLabel))) .collect(Collectors.toList()); for (DiagnosticResolution resolution : resolutions) { + + result.add(Either.forRight(createFix(resolution, diagnostic))); } } @@ -71,6 +73,8 @@ private CodeAction createFix(DiagnosticResolution resolution, Diagnostic diagnos CodeAction codeAction = new CodeAction(); codeAction.setDiagnostics(Collections.singletonList(diagnostic)); codeAction.setTitle(resolution.getLabel()); + // This causes very slow perf as the fix is applied in memory before needed. + // There needs to be another mechanism to do this. codeAction.setEdit(resolution.apply()); codeAction.setKind(CodeActionKind.QuickFix); From d95ce85aecb4c59bdfcabe67e2d61e42e313bfa0 Mon Sep 17 00:00:00 2001 From: Minesh Patel Date: Tue, 7 Jan 2025 09:05:22 +0000 Subject: [PATCH 3/3] Added lazy code assist --- .../ide/quickfix/ConstructorQuickFix.java | 20 +- .../RosettaQuickFixCodeActionService.java | 29 +- .../ide/quickfix/RosettaQuickFixProvider.java | 8 + .../ide/server/RosettaLanguageServerImpl.java | 362 +++++++++++------- 4 files changed, 260 insertions(+), 159 deletions(-) diff --git a/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/ConstructorQuickFix.java b/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/ConstructorQuickFix.java index 16af32e2d..454bd6b56 100644 --- a/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/ConstructorQuickFix.java +++ b/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/ConstructorQuickFix.java @@ -12,6 +12,7 @@ import com.regnosys.rosetta.types.RosettaTypeProvider; import javax.inject.Inject; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -23,7 +24,20 @@ public class ConstructorQuickFix { private RosettaTypeProvider types; @Inject private RosettaEcoreUtil extensions; - + + public void modifyConstructorWithAllAttributes(RosettaConstructorExpression constructor) { + RosettaFeatureGroup rosettaFeatureGroup = groupConstructorFeatures(constructor); + List allAttributes = rosettaFeatureGroup.allAttributes(); + + allAttributes.forEach(attr -> { + ConstructorKeyValuePair constructorKeyValuePair = ExpressionFactory.eINSTANCE.createConstructorKeyValuePair(); + constructorKeyValuePair.setKey(attr); + constructorKeyValuePair.setValue(ExpressionFactory.eINSTANCE.createListLiteral()); + constructor.getValues().add(constructorKeyValuePair); + }); + } + + public void modifyConstructorWithMandatoryAttributes(RosettaConstructorExpression constructor) { RosettaFeatureGroup rosettaFeatureGroup = groupConstructorFeatures(constructor); List requiredAbsentAttributes = rosettaFeatureGroup.requiredAbsentAttributes(); @@ -72,6 +86,10 @@ private RosettaFeatureGroup(List populated, List this.all = all; } + private List allAttributes() { + return all.stream().filter(x -> !populated.contains(x)).collect(Collectors.toList()); + } + private List requiredAbsentAttributes() { return all.stream() .filter(x -> !populated.contains(x)) diff --git a/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/RosettaQuickFixCodeActionService.java b/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/RosettaQuickFixCodeActionService.java index ad5777fb6..23e7284ad 100644 --- a/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/RosettaQuickFixCodeActionService.java +++ b/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/RosettaQuickFixCodeActionService.java @@ -46,38 +46,47 @@ public class RosettaQuickFixCodeActionService implements ICodeActionService2 { @Override public List> getCodeActions(Options options) { - boolean handleQuickfixes = options.getCodeActionParams().getContext().getOnly() == null - || options.getCodeActionParams().getContext().getOnly().isEmpty() - || options.getCodeActionParams().getContext().getOnly().contains(CodeActionKind.QuickFix); + CodeActionParams codeActionParams = options.getCodeActionParams(); + boolean handleQuickfixes = codeActionParams.getContext().getOnly() == null + || codeActionParams.getContext().getOnly().isEmpty() + || codeActionParams.getContext().getOnly().contains(CodeActionKind.QuickFix); if (!handleQuickfixes) { return Collections.emptyList(); } List> result = new ArrayList<>(); - for (Diagnostic diagnostic : options.getCodeActionParams().getContext().getDiagnostics()) { + for (Diagnostic diagnostic : codeActionParams.getContext().getDiagnostics()) { Options diagnosticOptions = createOptionsForSingleDiagnostic(options, diagnostic); + List resolutions = quickfixes.getResolutions(diagnosticOptions, diagnostic).stream() .sorted(Comparator.nullsLast(Comparator.comparing(DiagnosticResolution::getLabel))) .collect(Collectors.toList()); for (DiagnosticResolution resolution : resolutions) { - - - result.add(Either.forRight(createFix(resolution, diagnostic))); +// result.add(Either.forRight(createFix(resolution, diagnostic))); + result.add(Either.forRight(createUnresolvedFix(resolution.getLabel(), codeActionParams, diagnostic))); } } return result; } + private CodeAction createUnresolvedFix(String label, CodeActionParams codeActionParams, Diagnostic diagnostic) { + CodeAction codeAction = new CodeAction(); + codeAction.setDiagnostics(Collections.singletonList(diagnostic)); + codeAction.setTitle(label); + codeAction.setData(codeActionParams); + codeAction.setKind(CodeActionKind.QuickFix); + return codeAction; + } + private CodeAction createFix(DiagnosticResolution resolution, Diagnostic diagnostic) { CodeAction codeAction = new CodeAction(); codeAction.setDiagnostics(Collections.singletonList(diagnostic)); codeAction.setTitle(resolution.getLabel()); // This causes very slow perf as the fix is applied in memory before needed. - // There needs to be another mechanism to do this. + // There needs to be another mechanism to do this. codeAction.setEdit(resolution.apply()); codeAction.setKind(CodeActionKind.QuickFix); - return codeAction; } @@ -98,5 +107,5 @@ private Options createOptionsForSingleDiagnostic(Options base, Diagnostic diagno return options; } - + } \ No newline at end of file diff --git a/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/RosettaQuickFixProvider.java b/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/RosettaQuickFixProvider.java index 5afbb9ee7..4085d79cf 100644 --- a/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/RosettaQuickFixProvider.java +++ b/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/RosettaQuickFixProvider.java @@ -79,4 +79,12 @@ public void missingAttributes(DiagnosticResolutionAcceptor acceptor) { acceptor.accept("Auto add mandatory attributes.", semanticModification); } + @QuickFix(RosettaIssueCodes.MISSING_MANDATORY_CONSTRUCTOR_ARGUMENT) + public void addAllMissingAttributes(DiagnosticResolutionAcceptor acceptor) { + ISemanticModification semanticModification = (Diagnostic diagnostic, EObject object) -> + context -> constructorQuickFix.modifyConstructorWithAllAttributes(getContainerOfType(object, RosettaConstructorExpression.class)); + acceptor.accept("Auto add all attributes.", semanticModification); + } + + } diff --git a/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/server/RosettaLanguageServerImpl.java b/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/server/RosettaLanguageServerImpl.java index aad31d7cf..eb505ddd3 100644 --- a/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/server/RosettaLanguageServerImpl.java +++ b/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/server/RosettaLanguageServerImpl.java @@ -16,10 +16,17 @@ package com.regnosys.rosetta.ide.server; +import com.google.common.collect.Iterables; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import org.eclipse.lsp4j.jsonrpc.json.MessageJsonHandler; +import org.eclipse.xtext.ide.editor.quickfix.DiagnosticResolution; +import org.eclipse.xtext.ide.editor.quickfix.IQuickFixProvider; import org.eclipse.xtext.ide.server.LanguageServerImpl; import org.eclipse.emf.common.util.URI; import org.eclipse.lsp4j.*; import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.xtext.ide.server.codeActions.ICodeActionService2; import org.eclipse.xtext.resource.IResourceServiceProvider; import org.eclipse.xtext.util.CancelIndicator; @@ -30,159 +37,218 @@ import com.regnosys.rosetta.ide.semantictokens.SemanticToken; import java.io.IOException; -import java.util.Arrays; -import java.util.List; +import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import javax.inject.Inject; /** * TODO: contribute to Xtext. - * */ -public class RosettaLanguageServerImpl extends LanguageServerImpl implements RosettaLanguageServer{ - @Inject FormattingOptionsAdaptor formattingOptionsAdapter; - - @Override - protected ServerCapabilities createServerCapabilities(InitializeParams params) { - ServerCapabilities serverCapabilities = super.createServerCapabilities(params); - IResourceServiceProvider resourceServiceProvider = getResourceServiceProvider(URI.createURI("synth:///file.rosetta")); - - if (resourceServiceProvider.get(IInlayHintsService.class) != null) { - InlayHintRegistrationOptions inlayHintRegistrationOptions = new InlayHintRegistrationOptions(); - inlayHintRegistrationOptions.setResolveProvider(resourceServiceProvider.get(IInlayHintsResolver.class) != null); - serverCapabilities.setInlayHintProvider(inlayHintRegistrationOptions); - } - - ISemanticTokensService semanticTokensService = resourceServiceProvider.get(ISemanticTokensService.class); - if (semanticTokensService != null) { - SemanticTokensWithRegistrationOptions semanticTokensOptions = new SemanticTokensWithRegistrationOptions(); - semanticTokensOptions.setLegend(semanticTokensService.getLegend()); - semanticTokensOptions.setFull(true); - semanticTokensOptions.setRange(true); - serverCapabilities.setSemanticTokensProvider(semanticTokensOptions); - } - - return serverCapabilities; - } - - /*** INLAY HINTS ***/ - - protected List inlayHint(InlayHintParams params, CancelIndicator cancelIndicator) { - URI uri = this.getURI(params.getTextDocument()); - return this.getWorkspaceManager().doRead(uri, (document, resource) -> { - IInlayHintsService service = getService(uri, IInlayHintsService.class); - List result = service.computeInlayHint(document, resource, params, cancelIndicator); - this.installInlayHintURI(result, uri.toString()); - return result; - }); - } - - @Override - public CompletableFuture> inlayHint(InlayHintParams params) { - return this.getRequestManager().runRead((cancelIndicator) -> this.inlayHint(params, cancelIndicator)); - } - - @Override - public CompletableFuture resolveInlayHint(InlayHint unresolved) { - URI uri = this.uninstallInlayHintURI(unresolved); - return uri == null ? CompletableFuture.completedFuture(unresolved) : this.getRequestManager() - .runRead((cancelIndicator) -> this.resolveInlayHint(uri, unresolved, cancelIndicator)); - } - - protected InlayHint resolveInlayHint(URI uri, InlayHint unresolved, CancelIndicator cancelIndicator) { - return this.getWorkspaceManager().doRead(uri, (document, resource) -> { - IInlayHintsResolver resolver = getService(uri, IInlayHintsResolver.class); - return resolver.resolveInlayHint(document, resource, unresolved, cancelIndicator); - }); - } - - protected void installInlayHintURI(List inlayHints, String uri) { - for (InlayHint inlayHint : inlayHints) { - Object data = inlayHint.getData(); - if (data != null) { - inlayHint.setData(Arrays.asList(uri, inlayHint.getData())); - } else { - inlayHint.setData(uri); - } - } - } - - protected URI uninstallInlayHintURI(InlayHint inlayHint) { - URI result = null; - Object data = inlayHint.getData(); - if (data instanceof String) { - result = URI.createURI(data.toString()); - inlayHint.setData(null); - } else if (data instanceof List) { - List l = (List) data; - result = URI.createURI(l.get(0).toString()); - inlayHint.setData(l.get(1)); - } - - return result; - } - - /*** SEMANTIC TOKENS ***/ - public List semanticTokens(SemanticTokensParams params, CancelIndicator cancelIndicator) { - URI uri = this.getURI(params.getTextDocument()); - return this.getWorkspaceManager().doRead(uri, (document, resource) -> { - ISemanticTokensService service = getService(uri, ISemanticTokensService.class); - return service.computeSemanticTokens(document, resource, params, cancelIndicator); - }); - } - - protected SemanticTokens semanticTokensFull(SemanticTokensParams params, CancelIndicator cancelIndicator) { - URI uri = this.getURI(params.getTextDocument()); - return this.getWorkspaceManager().doRead(uri, (document, resource) -> { - ISemanticTokensService service = getService(uri, ISemanticTokensService.class); - List tokens = service.computeSemanticTokens(document, resource, params, cancelIndicator); - SemanticTokens result = service.toSemanticTokensResponse(tokens); - return result; - }); - } - - /** - * LSP method: textDocument/semanticTokens/full - */ - @Override - public CompletableFuture semanticTokensFull(SemanticTokensParams params) { - return this.getRequestManager().runRead((cancelIndicator) -> this.semanticTokensFull(params, cancelIndicator)); - } - - /** - * LSP method: textDocument/semanticTokens/full/delta - */ - @Override - public CompletableFuture> semanticTokensFullDelta(SemanticTokensDeltaParams params) { - throw new UnsupportedOperationException(); - } - - protected SemanticTokens semanticTokensRange(SemanticTokensRangeParams params, CancelIndicator cancelIndicator) { - URI uri = this.getURI(params.getTextDocument()); - return this.getWorkspaceManager().doRead(uri, (document, resource) -> { - ISemanticTokensService service = getService(uri, ISemanticTokensService.class); - List tokens = service.computeSemanticTokensInRange(document, resource, params, cancelIndicator); - SemanticTokens result = service.toSemanticTokensResponse(tokens); - return result; - }); - } - - /** - * LSP method: textDocument/semanticTokens/range - */ - @Override - public CompletableFuture semanticTokensRange(SemanticTokensRangeParams params) { - return this.getRequestManager().runRead((cancelIndicator) -> this.semanticTokensRange(params, cancelIndicator)); - } - - @Override - public CompletableFuture getDefaultFormattingOptions() { - try { - return CompletableFuture.completedFuture(formattingOptionsAdapter.readFormattingOptions(null)); - } catch (IOException e) { - // should never happen, since null path always leads to default options being returned - return CompletableFuture.failedFuture(e); - } - } +public class RosettaLanguageServerImpl extends LanguageServerImpl implements RosettaLanguageServer { + @Inject + FormattingOptionsAdaptor formattingOptionsAdapter; + + @Override + protected ServerCapabilities createServerCapabilities(InitializeParams params) { + ServerCapabilities serverCapabilities = super.createServerCapabilities(params); + IResourceServiceProvider resourceServiceProvider = getResourceServiceProvider(URI.createURI("synth:///file.rosetta")); + + if (resourceServiceProvider.get(IInlayHintsService.class) != null) { + InlayHintRegistrationOptions inlayHintRegistrationOptions = new InlayHintRegistrationOptions(); + inlayHintRegistrationOptions.setResolveProvider(resourceServiceProvider.get(IInlayHintsResolver.class) != null); + serverCapabilities.setInlayHintProvider(inlayHintRegistrationOptions); + } + + if (resourceServiceProvider.get(ICodeActionService2.class) != null) { + CodeActionOptions codeActionProvider = new CodeActionOptions(); + codeActionProvider.setResolveProvider(true); + codeActionProvider.setCodeActionKinds(List.of(CodeActionKind.QuickFix)); + codeActionProvider.setWorkDoneProgress(true); + serverCapabilities.setCodeActionProvider(codeActionProvider); + } + + ISemanticTokensService semanticTokensService = resourceServiceProvider.get(ISemanticTokensService.class); + if (semanticTokensService != null) { + SemanticTokensWithRegistrationOptions semanticTokensOptions = new SemanticTokensWithRegistrationOptions(); + semanticTokensOptions.setLegend(semanticTokensService.getLegend()); + semanticTokensOptions.setFull(true); + semanticTokensOptions.setRange(true); + serverCapabilities.setSemanticTokensProvider(semanticTokensOptions); + } + + return serverCapabilities; + } + + /*** INLAY HINTS ***/ + + protected List inlayHint(InlayHintParams params, CancelIndicator cancelIndicator) { + URI uri = this.getURI(params.getTextDocument()); + return this.getWorkspaceManager().doRead(uri, (document, resource) -> { + IInlayHintsService service = getService(uri, IInlayHintsService.class); + List result = service.computeInlayHint(document, resource, params, cancelIndicator); + this.installInlayHintURI(result, uri.toString()); + return result; + }); + } + + @Override + public CompletableFuture> inlayHint(InlayHintParams params) { + return this.getRequestManager().runRead((cancelIndicator) -> this.inlayHint(params, cancelIndicator)); + } + + @Override + public CompletableFuture resolveInlayHint(InlayHint unresolved) { + URI uri = this.uninstallInlayHintURI(unresolved); + return uri == null ? CompletableFuture.completedFuture(unresolved) : this.getRequestManager() + .runRead((cancelIndicator) -> this.resolveInlayHint(uri, unresolved, cancelIndicator)); + } + + protected InlayHint resolveInlayHint(URI uri, InlayHint unresolved, CancelIndicator cancelIndicator) { + return this.getWorkspaceManager().doRead(uri, (document, resource) -> { + IInlayHintsResolver resolver = getService(uri, IInlayHintsResolver.class); + return resolver.resolveInlayHint(document, resource, unresolved, cancelIndicator); + }); + } + + protected void installInlayHintURI(List inlayHints, String uri) { + for (InlayHint inlayHint : inlayHints) { + Object data = inlayHint.getData(); + if (data != null) { + inlayHint.setData(Arrays.asList(uri, inlayHint.getData())); + } else { + inlayHint.setData(uri); + } + } + } + + protected URI uninstallInlayHintURI(InlayHint inlayHint) { + URI result = null; + Object data = inlayHint.getData(); + if (data instanceof String) { + result = URI.createURI(data.toString()); + inlayHint.setData(null); + } else if (data instanceof List) { + List l = (List) data; + result = URI.createURI(l.get(0).toString()); + inlayHint.setData(l.get(1)); + } + + return result; + } + + /*** SEMANTIC TOKENS ***/ + public List semanticTokens(SemanticTokensParams params, CancelIndicator cancelIndicator) { + URI uri = this.getURI(params.getTextDocument()); + return this.getWorkspaceManager().doRead(uri, (document, resource) -> { + ISemanticTokensService service = getService(uri, ISemanticTokensService.class); + return service.computeSemanticTokens(document, resource, params, cancelIndicator); + }); + } + + protected SemanticTokens semanticTokensFull(SemanticTokensParams params, CancelIndicator cancelIndicator) { + URI uri = this.getURI(params.getTextDocument()); + return this.getWorkspaceManager().doRead(uri, (document, resource) -> { + ISemanticTokensService service = getService(uri, ISemanticTokensService.class); + List tokens = service.computeSemanticTokens(document, resource, params, cancelIndicator); + SemanticTokens result = service.toSemanticTokensResponse(tokens); + return result; + }); + } + + /** + * LSP method: textDocument/semanticTokens/full + */ + @Override + public CompletableFuture semanticTokensFull(SemanticTokensParams params) { + return this.getRequestManager().runRead((cancelIndicator) -> this.semanticTokensFull(params, cancelIndicator)); + } + + /** + * LSP method: textDocument/semanticTokens/full/delta + */ + @Override + public CompletableFuture> semanticTokensFullDelta(SemanticTokensDeltaParams params) { + throw new UnsupportedOperationException(); + } + + protected SemanticTokens semanticTokensRange(SemanticTokensRangeParams params, CancelIndicator cancelIndicator) { + URI uri = this.getURI(params.getTextDocument()); + return this.getWorkspaceManager().doRead(uri, (document, resource) -> { + ISemanticTokensService service = getService(uri, ISemanticTokensService.class); + List tokens = service.computeSemanticTokensInRange(document, resource, params, cancelIndicator); + SemanticTokens result = service.toSemanticTokensResponse(tokens); + return result; + }); + } + + /** + * LSP method: textDocument/semanticTokens/range + */ + @Override + public CompletableFuture semanticTokensRange(SemanticTokensRangeParams params) { + return this.getRequestManager().runRead((cancelIndicator) -> this.semanticTokensRange(params, cancelIndicator)); + } + + @Override + public CompletableFuture getDefaultFormattingOptions() { + try { + return CompletableFuture.completedFuture(formattingOptionsAdapter.readFormattingOptions(null)); + } catch (IOException e) { + // should never happen, since null path always leads to default options being returned + return CompletableFuture.failedFuture(e); + } + } + + @Override + public CompletableFuture resolveCodeAction(CodeAction unresolved) { + return getRequestManager().runRead((cancelIndicator) -> resolveCodeAction(unresolved, cancelIndicator)); + } + + protected CodeAction resolveCodeAction(CodeAction codeAction, CancelIndicator cancelIndicator) { + CodeActionParams codeActionParams = getCodeActionParams(codeAction); + + if (codeActionParams.getTextDocument() == null) { + return null; + } + + URI uri = URI.createURI(codeActionParams.getTextDocument().getUri()); + + IQuickFixProvider quickfixes = getService(uri, IQuickFixProvider.class); + + return getWorkspaceManager().doRead(uri, (doc, resource) -> { + ICodeActionService2.Options options = new ICodeActionService2.Options(); + options.setDocument(doc); + options.setResource(resource); + options.setLanguageServerAccess(getLanguageServerAccess()); + options.setCodeActionParams(codeActionParams); + options.setCancelIndicator(cancelIndicator); + + Diagnostic diagnostic = Iterables.getOnlyElement(codeAction.getDiagnostics()); + List resolutions = quickfixes.getResolutions(options, diagnostic).stream() + .sorted(Comparator.nullsLast(Comparator.comparing(DiagnosticResolution::getLabel))) + .collect(Collectors.toList()); + // wrong. + resolutions.stream() + .filter(r -> r.getLabel().equals(codeAction.getTitle())) + .findFirst() + .ifPresent(r -> codeAction.setEdit(r.apply())); + + return codeAction; + }); + } + + protected CodeActionParams getCodeActionParams(CodeAction codeAction) { + Object data = codeAction.getData(); + CodeActionParams codeActionParams = null; + if (data instanceof CodeActionParams) { + codeActionParams = (CodeActionParams) data; + } + if (data instanceof JsonObject) { + Gson gson = new MessageJsonHandler(Map.of()).getGson(); + codeActionParams = gson.fromJson(((JsonObject) data), CodeActionParams.class); + } + return codeActionParams; + } } \ No newline at end of file