diff --git a/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/formatting/RosettaFormattingService.java b/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/formatting/RosettaFormattingService.java
index 38c5999fd..a1045f4cd 100644
--- a/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/formatting/RosettaFormattingService.java
+++ b/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/formatting/RosettaFormattingService.java
@@ -24,18 +24,15 @@
import org.eclipse.lsp4j.FormattingOptions;
import org.eclipse.lsp4j.TextEdit;
-import org.eclipse.xtext.formatting.IIndentationInformation;
import org.eclipse.xtext.formatting2.IFormatter2;
import org.eclipse.xtext.formatting2.regionaccess.ITextReplacement;
import org.eclipse.xtext.ide.server.Document;
import org.eclipse.xtext.ide.server.formatting.FormattingService;
import org.eclipse.xtext.preferences.ITypedPreferenceValues;
-import org.eclipse.xtext.preferences.MapBasedPreferenceValues;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.util.TextRegion;
-import com.google.common.base.Strings;
-import com.regnosys.rosetta.formatting2.RosettaFormatterPreferenceKeys;
+import com.regnosys.rosetta.formatting2.FormattingOptionsAdaptor;
/**
* This class allows passing additional formatting parameters as defined in
@@ -47,55 +44,17 @@
* - expose injected fields to child classes (make them protected)
*/
public class RosettaFormattingService extends FormattingService {
- public static String PREFERENCE_INDENTATION_KEY = "indentation";
- public static String PREFERENCE_MAX_LINE_WIDTH_KEY = "maxLineWidth";
- public static String PREFERENCE_CONDITIONAL_MAX_LINE_WIDTH_KEY = "conditionalMaxLineWidth";
-
@Inject
private Provider formatter2Provider;
-
@Inject
- private IIndentationInformation indentationInformation;
-
- protected ITypedPreferenceValues createPreferences(FormattingOptions options) {
- MapBasedPreferenceValues preferences = new MapBasedPreferenceValues();
-
- String indent = indentationInformation.getIndentString();
- if (options != null) {
- if (options.isInsertSpaces()) {
- indent = Strings.padEnd("", options.getTabSize(), ' ');
- }
- }
- preferences.put(PREFERENCE_INDENTATION_KEY, indent);
-
- if (options == null) {
- return preferences;
- }
+ private FormattingOptionsAdaptor formattingOptionsAdapter;
- Number conditionalMaxLineWidth = options.getNumber(PREFERENCE_CONDITIONAL_MAX_LINE_WIDTH_KEY);
- if (conditionalMaxLineWidth != null) {
- preferences.put(RosettaFormatterPreferenceKeys.conditionalMaxLineWidth, conditionalMaxLineWidth.intValue());
- }
- Number maxLineWidth = options.getNumber(PREFERENCE_MAX_LINE_WIDTH_KEY);
- if (maxLineWidth != null) {
- preferences.put(RosettaFormatterPreferenceKeys.maxLineWidth, maxLineWidth.intValue());
- if (conditionalMaxLineWidth == null) {
- int defaultConditionalMaxLineWidth = RosettaFormatterPreferenceKeys.conditionalMaxLineWidth.toValue(RosettaFormatterPreferenceKeys.conditionalMaxLineWidth.getDefaultValue());
- int defaultMaxLineWidth = RosettaFormatterPreferenceKeys.maxLineWidth.toValue(RosettaFormatterPreferenceKeys.maxLineWidth.getDefaultValue());
- double defaultRatio = (double)defaultConditionalMaxLineWidth / defaultMaxLineWidth;
- preferences.put(RosettaFormatterPreferenceKeys.conditionalMaxLineWidth, (int)(maxLineWidth.doubleValue() * defaultRatio));
- }
- }
-
- return preferences;
- }
-
@Override
public List format(XtextResource resource, Document document, int offset, int length,
FormattingOptions options) {
List result = new ArrayList<>();
if (this.formatter2Provider != null) {
- ITypedPreferenceValues preferences = createPreferences(options);
+ ITypedPreferenceValues preferences = formattingOptionsAdapter.createPreferences(options);
List replacements = format2(resource, new TextRegion(offset, length), preferences);
for (ITextReplacement r : replacements) {
result.add(toTextEdit(document, r.getReplacementText(), r.getOffset(), r.getLength()));
diff --git a/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/server/RosettaLanguageServer.java b/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/server/RosettaLanguageServer.java
new file mode 100644
index 000000000..6aa54234f
--- /dev/null
+++ b/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/server/RosettaLanguageServer.java
@@ -0,0 +1,13 @@
+package com.regnosys.rosetta.ide.server;
+
+import java.util.concurrent.CompletableFuture;
+
+import org.eclipse.lsp4j.FormattingOptions;
+import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;
+import org.eclipse.lsp4j.services.LanguageServer;
+
+public interface RosettaLanguageServer extends LanguageServer{
+
+ @JsonRequest
+ CompletableFuture getDefaultFormattingOptions();
+}
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 609272052..aad31d7cf 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
@@ -23,20 +23,25 @@
import org.eclipse.xtext.resource.IResourceServiceProvider;
import org.eclipse.xtext.util.CancelIndicator;
+import com.regnosys.rosetta.formatting2.FormattingOptionsAdaptor;
import com.regnosys.rosetta.ide.inlayhints.IInlayHintsResolver;
import com.regnosys.rosetta.ide.inlayhints.IInlayHintsService;
import com.regnosys.rosetta.ide.semantictokens.ISemanticTokensService;
import com.regnosys.rosetta.ide.semantictokens.SemanticToken;
+import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
+import javax.inject.Inject;
+
/**
* TODO: contribute to Xtext.
*
*/
-public class RosettaLanguageServerImpl extends LanguageServerImpl {
+public class RosettaLanguageServerImpl extends LanguageServerImpl implements RosettaLanguageServer{
+ @Inject FormattingOptionsAdaptor formattingOptionsAdapter;
@Override
protected ServerCapabilities createServerCapabilities(InitializeParams params) {
@@ -170,4 +175,14 @@ protected SemanticTokens semanticTokensRange(SemanticTokensRangeParams params, C
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);
+ }
+ }
}
\ No newline at end of file
diff --git a/rosetta-ide/src/test/java/com/regnosys/rosetta/ide/formatting/FormattingTest.xtend b/rosetta-ide/src/test/java/com/regnosys/rosetta/ide/formatting/FormattingTest.xtend
index 9c78185f0..7c20e52f0 100644
--- a/rosetta-ide/src/test/java/com/regnosys/rosetta/ide/formatting/FormattingTest.xtend
+++ b/rosetta-ide/src/test/java/com/regnosys/rosetta/ide/formatting/FormattingTest.xtend
@@ -3,6 +3,7 @@ package com.regnosys.rosetta.ide.formatting
import org.junit.jupiter.api.Test
import com.regnosys.rosetta.ide.tests.AbstractRosettaLanguageServerTest
import org.eclipse.lsp4j.FormattingOptions
+import com.regnosys.rosetta.formatting2.FormattingOptionsAdaptor
class FormattingTest extends AbstractRosettaLanguageServerTest {
@Test
@@ -23,7 +24,7 @@ class FormattingTest extends AbstractRosettaLanguageServerTest {
testFormatting(
[
val options = new FormattingOptions
- options.putNumber(RosettaFormattingService.PREFERENCE_MAX_LINE_WIDTH_KEY, 10)
+ options.putNumber(FormattingOptionsAdaptor.PREFERENCE_MAX_LINE_WIDTH_KEY, 10)
it.options = options
],
[
@@ -67,7 +68,7 @@ class FormattingTest extends AbstractRosettaLanguageServerTest {
testFormatting(
[
val options = new FormattingOptions
- options.putNumber(RosettaFormattingService.PREFERENCE_CONDITIONAL_MAX_LINE_WIDTH_KEY, 10)
+ options.putNumber(FormattingOptionsAdaptor.PREFERENCE_CONDITIONAL_MAX_LINE_WIDTH_KEY, 10)
it.options = options
],
[
diff --git a/rosetta-lang/model/RosettaExpression.xcore b/rosetta-lang/model/RosettaExpression.xcore
index 2d6b2fee9..dac8f7362 100644
--- a/rosetta-lang/model/RosettaExpression.xcore
+++ b/rosetta-lang/model/RosettaExpression.xcore
@@ -244,6 +244,7 @@ class JoinOperation extends RosettaBinaryOperation {
class RosettaOnlyExistsExpression extends RosettaExpression {
contains RosettaExpression[] args
+ boolean hasParentheses
}
/**
diff --git a/rosetta-lang/pom.xml b/rosetta-lang/pom.xml
index 8686c0ae5..7321b1f45 100644
--- a/rosetta-lang/pom.xml
+++ b/rosetta-lang/pom.xml
@@ -71,6 +71,10 @@
com.fasterxml.jackson.dataformat
jackson-dataformat-yaml
+
+ org.eclipse.lsp4j
+ org.eclipse.lsp4j
+
diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/Rosetta.xtext b/rosetta-lang/src/main/java/com/regnosys/rosetta/Rosetta.xtext
index f5a54d05c..76a07fba5 100644
--- a/rosetta-lang/src/main/java/com/regnosys/rosetta/Rosetta.xtext
+++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/Rosetta.xtext
@@ -789,7 +789,7 @@ RosettaCalcConditionalExpression returns RosettaExpression:
;
RosettaCalcOnlyExists returns RosettaExpression:
- {RosettaOnlyExistsExpression} (args+=RosettaOnlyExistsElement | ('(' args+=RosettaOnlyExistsElement (',' args+=RosettaOnlyExistsElement)* ')')) 'only' 'exists'
+ {RosettaOnlyExistsExpression} (args+=RosettaOnlyExistsElement | (hasParentheses?='(' args+=RosettaOnlyExistsElement (',' args+=RosettaOnlyExistsElement)* ')')) 'only' 'exists'
;
RosettaOnlyExistsElement returns RosettaExpression:
diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/FormattingOptionsAdaptor.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/FormattingOptionsAdaptor.java
new file mode 100644
index 000000000..81a591275
--- /dev/null
+++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/FormattingOptionsAdaptor.java
@@ -0,0 +1,94 @@
+package com.regnosys.rosetta.formatting2;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+import org.eclipse.emf.mwe.core.resources.ResourceLoader;
+import org.eclipse.lsp4j.FormattingOptions;
+import org.eclipse.xtext.preferences.ITypedPreferenceValues;
+import org.eclipse.xtext.preferences.MapBasedPreferenceValues;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Strings;
+
+public class FormattingOptionsAdaptor {
+ public static String PREFERENCE_INDENTATION_KEY = "indentation";
+ public static String PREFERENCE_MAX_LINE_WIDTH_KEY = "maxLineWidth";
+ public static String PREFERENCE_CONDITIONAL_MAX_LINE_WIDTH_KEY = "conditionalMaxLineWidth";
+
+ private static final String DEFAULT_FORMATTING_OPTIONS_PATH = "default-formatting-options.json";
+
+ public ITypedPreferenceValues createPreferences(FormattingOptions options) {
+ MapBasedPreferenceValues preferences = new MapBasedPreferenceValues();
+
+ String indent = "\t";
+ if (options != null) {
+ if (options.isInsertSpaces()) {
+ indent = Strings.padEnd("", options.getTabSize(), ' ');
+ }
+ }
+ preferences.put(PREFERENCE_INDENTATION_KEY, indent);
+
+ if (options == null) {
+ return preferences;
+ }
+
+ Number conditionalMaxLineWidth = options.getNumber(PREFERENCE_CONDITIONAL_MAX_LINE_WIDTH_KEY);
+ if (conditionalMaxLineWidth != null) {
+ preferences.put(RosettaFormatterPreferenceKeys.conditionalMaxLineWidth, conditionalMaxLineWidth.intValue());
+ }
+ Number maxLineWidth = options.getNumber(PREFERENCE_MAX_LINE_WIDTH_KEY);
+ if (maxLineWidth != null) {
+ preferences.put(RosettaFormatterPreferenceKeys.maxLineWidth, maxLineWidth.intValue());
+ if (conditionalMaxLineWidth == null) {
+ int defaultConditionalMaxLineWidth = RosettaFormatterPreferenceKeys.conditionalMaxLineWidth
+ .toValue(RosettaFormatterPreferenceKeys.conditionalMaxLineWidth.getDefaultValue());
+ int defaultMaxLineWidth = RosettaFormatterPreferenceKeys.maxLineWidth
+ .toValue(RosettaFormatterPreferenceKeys.maxLineWidth.getDefaultValue());
+ double defaultRatio = (double) defaultConditionalMaxLineWidth / defaultMaxLineWidth;
+ preferences.put(RosettaFormatterPreferenceKeys.conditionalMaxLineWidth,
+ (int) (maxLineWidth.doubleValue() * defaultRatio));
+ }
+ }
+
+ return preferences;
+ }
+
+ public FormattingOptions readFormattingOptions(String optionsPath) throws IOException {
+ InputStream resourceStream;
+ // If path not given, use default one
+ if (optionsPath == null) {
+ // Retrieve resource as an InputStream
+ resourceStream = ResourceLoader.class.getClassLoader().getResourceAsStream(DEFAULT_FORMATTING_OPTIONS_PATH);
+ } else {
+ resourceStream = new FileInputStream(optionsPath);
+ }
+
+ // Create an ObjectMapper, read JSON into a Map
+ ObjectMapper objectMapper = new ObjectMapper();
+ Map map = null;
+ map = objectMapper.readValue(resourceStream, Map.class);
+
+ // Create a FormattingOptions object
+ FormattingOptions formattingOptions = new FormattingOptions();
+
+ // Populate the FormattingOptions object
+ for (Map.Entry entry : map.entrySet()) {
+ String key = entry.getKey();
+ Object value = entry.getValue();
+
+ if (value instanceof String) {
+ formattingOptions.putString(key, (String) value);
+ } else if (value instanceof Number) {
+ formattingOptions.putNumber(key, (Number) value);
+ } else if (value instanceof Boolean) {
+ formattingOptions.putBoolean(key, (Boolean) value);
+ } else {
+ throw new IllegalArgumentException("Unsupported value type for key: " + key);
+ }
+ }
+ return formattingOptions;
+ }
+}
diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/FormattingUtil.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/FormattingUtil.java
index 7561cc647..0c21d2316 100644
--- a/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/FormattingUtil.java
+++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/FormattingUtil.java
@@ -29,7 +29,7 @@
import org.eclipse.xtext.preferences.TypedPreferenceKey;
public class FormattingUtil {
- private ITextRegionExtensions getTextRegionExt(IFormattableDocument doc) {
+ public ITextRegionExtensions getTextRegionExt(IFormattableDocument doc) {
return doc.getRequest().getTextRegionAccess().getExtensions();
}
diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/IFormattedResourceAcceptor.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/IFormattedResourceAcceptor.java
new file mode 100644
index 000000000..f122226fa
--- /dev/null
+++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/IFormattedResourceAcceptor.java
@@ -0,0 +1,25 @@
+package com.regnosys.rosetta.formatting2;
+
+import org.eclipse.emf.ecore.resource.Resource;
+
+/**
+ * Functional interface for handling a formatted resource and its corresponding
+ * formatted text.
+ *
+ * This interface allows customization of how formatted resources are processed,
+ * such as saving the content, logging it, or using it for validations or
+ * assertions in tests.
+ *
+ */
+@FunctionalInterface
+public interface IFormattedResourceAcceptor {
+
+ /**
+ * Accepts a formatted resource and its formatted content for further
+ * processing.
+ *
+ * @param resource the {@link Resource} that was formatted
+ * @param formattedText the formatted content as a {@link String}
+ */
+ void accept(Resource resource, String formattedText);
+}
diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/ResourceFormatterService.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/ResourceFormatterService.java
index fd6633330..7f9337d58 100644
--- a/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/ResourceFormatterService.java
+++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/ResourceFormatterService.java
@@ -7,64 +7,124 @@
import org.eclipse.xtext.resource.XtextResource;
public interface ResourceFormatterService {
-
+
/**
- * Formats each {@link XtextResource} in the provided collection in-memory.
+ * Formats each {@link XtextResource} in the provided collection in-memory and
+ * passes the formatted content to a handler for further processing.
*
- * This method iterates over the given collection of resources and applies formatting
- * directly to each resource. Formatting may include indentation, spacing adjustments,
- * and other stylistic improvements to ensure consistency and readability of the resources.
+ * This method iterates over the given collection of resources and applies
+ * formatting directly to each resource. Formatting may include indentation,
+ * spacing adjustments, and other stylistic improvements to ensure consistency
+ * and readability of the resources.
*
*
- * @param resources a collection of {@link XtextResource} objects to be formatted
+ *
+ * The handler, represented as a {@link java.util.function.BiConsumer}, is
+ * called with two arguments: the {@link Resource} being formatted, and the
+ * resulting formatted text as a {@link String}. This allows the caller to
+ * specify actions such as saving the formatted content, logging it, or
+ * collecting it for assertions in a test.
+ *
+ *
+ * @param resources a collection of {@link XtextResource} objects to be
+ * formatted
+ * @param acceptor an {@link IFormattedResourceAcceptor} to process the
+ * formatted resource and its content
*/
- default void formatCollection(Collection resources) {
- formatCollection(resources, null);
+ default void formatCollection(Collection resources, IFormattedResourceAcceptor acceptor) {
+ formatCollection(resources, null, acceptor);
}
-
+
/**
- * Formats the given {@link XtextResource} in-memory.
+ * Formats the given {@link XtextResource} in-memory and passes the formatted
+ * content to a handler for further processing.
*
- * This method applies formatting directly to the specified resource. Formatting can include
- * adjustments to indentation, spacing, and other stylistic elements to ensure consistency
- * and readability of the resource content.
+ * This method applies formatting directly to the specified resource. Formatting
+ * can include adjustments to indentation, spacing, and other stylistic elements
+ * to ensure consistency and readability of the resource content.
+ *
+ *
+ *
+ * The handler, represented as a {@link java.util.function.BiConsumer}, is
+ * called with two arguments: the {@link Resource} being formatted, and the
+ * resulting formatted text as a {@link String}. This allows the caller to
+ * specify actions such as saving the formatted content, logging it, or
+ * collecting it for assertions in a test.
*
*
* @param resources the {@link XtextResource} to format
- * @param preferenceValues an {@link ITypedPreferenceValues} object containing formatting preferences,
- * or {@code null} if no preferences are specified
+ * @param acceptor an {@link IFormattedResourceAcceptor} to process the
+ * formatted resource and its content
*/
- default void formatXtextResource(XtextResource resource) {
- formatXtextResource(resource, null);
+ default void formatXtextResource(XtextResource resource, IFormattedResourceAcceptor acceptor) {
+ formatXtextResource(resource, null, acceptor);
}
-
+
/**
- * Formats each {@link XtextResource} in the provided collection in-memory, with specified formatting preferences.
+ * Formats each {@link XtextResource} in the provided collection in-memory,
+ * applying specified formatting preferences, and passes the formatted content
+ * to a handler for further processing.
*
- * This method iterates over the given collection of resources and applies formatting
- * directly to each resource. Formatting may include indentation, spacing adjustments,
- * and other stylistic improvements to ensure consistency and readability of the resources.
- * The formatting can be customized based on the specified {@link ITypedPreferenceValues}.
- * If no preferences are required, {@code preferenceValues} can be set to {@code null}.
+ * This method iterates over the given collection of resources, formats each
+ * resource according to the provided preferences, and invokes a handler to
+ * process the formatted content. Formatting includes indentation, spacing
+ * adjustments, and other stylistic refinements to ensure consistency and
+ * readability of the resources. The formatting behavior can be customized based
+ * on the provided {@link ITypedPreferenceValues}.
+ *
+ *
+ * The handler, represented as a {@link java.util.function.BiConsumer}, is
+ * called for each resource with two arguments: the {@link Resource} being
+ * formatted, and the resulting formatted text as a {@link String}. This allows
+ * the caller to specify actions such as saving the formatted content, logging
+ * it, or collecting it for assertions in a test.
+ *
+ *
+ * If no formatting preferences are required, the {@code preferenceValues}
+ * parameter can be set to {@code null}.
*
*
- * @param resources a collection of {@link XtextResource} objects to be formatted
- * @param preferenceValues an {@link ITypedPreferenceValues} object containing formatting preferences,
- * or {@code null} if no preferences are specified
+ * @param resources a collection of {@link XtextResource} objects to be
+ * formatted
+ * @param preferenceValues an {@link ITypedPreferenceValues} object containing
+ * formatting preferences, or {@code null} if no
+ * preferences are specified
+ * @param acceptor an {@link IFormattedResourceAcceptor} to process the
+ * formatted resource and its content
*/
- void formatCollection(Collection resources, ITypedPreferenceValues preferenceValues);
-
+ void formatCollection(Collection resources, ITypedPreferenceValues preferenceValues,
+ IFormattedResourceAcceptor acceptor);
+
/**
- * Formats the given {@link XtextResource} in-memory.
+ * Formats the given {@link XtextResource} in-memory, applying specified
+ * formatting preferences, and passes the formatted content to a handler for
+ * further processing.
*
- * This method applies formatting directly to the specified resource. Formatting can include
- * adjustments to indentation, spacing, and other stylistic elements to ensure consistency
- * and readability of the resource content.
- * The formatting can be customized based on the specified {@link ITypedPreferenceValues}.
- * If no preferences are required, {@code preferenceValues} can be set to {@code null}.
+ * This method formats each resource according to the provided preferences, and
+ * invokes a handler to process the formatted content. Formatting includes
+ * indentation, spacing adjustments, and other stylistic refinements to ensure
+ * consistency and readability of the resources. The formatting behavior can be
+ * customized based on the provided {@link ITypedPreferenceValues}.
*
- *
- * @param resource the {@link XtextResource} to format
+ *
+ * The handler, represented as a {@link java.util.function.BiConsumer}, is
+ * called with two arguments: the {@link Resource} being formatted, and the
+ * resulting formatted text as a {@link String}. This allows the caller to
+ * specify actions such as saving the formatted content, logging it, or
+ * collecting it for assertions in a test.
+ *
+ *
+ * If no formatting preferences are required, the {@code preferenceValues}
+ * parameter can be set to {@code null}.
+ *
+ *
+ * @param resource the {@link XtextResource} to be formatted
+ * @param preferenceValues an {@link ITypedPreferenceValues} object containing
+ * formatting preferences, or {@code null} if no
+ * preferences are specified
+ * @param acceptor an {@link IFormattedResourceAcceptor} to process the
+ * formatted resource and its content
*/
- void formatXtextResource(XtextResource resource, ITypedPreferenceValues preferenceValues);
+ void formatXtextResource(XtextResource resource, ITypedPreferenceValues preferenceValues,
+ IFormattedResourceAcceptor acceptor);
}
diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/RosettaExpressionFormatter.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/RosettaExpressionFormatter.xtend
index 5e497bd88..38a88b42b 100644
--- a/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/RosettaExpressionFormatter.xtend
+++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/RosettaExpressionFormatter.xtend
@@ -31,6 +31,10 @@ import com.regnosys.rosetta.rosetta.expression.RosettaOperation
import com.regnosys.rosetta.rosetta.expression.ThenOperation
import com.regnosys.rosetta.rosetta.expression.RosettaConstructorExpression
import com.regnosys.rosetta.rosetta.expression.RosettaDeepFeatureCall
+import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion
+import org.eclipse.emf.ecore.EObject
+import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion
+import com.regnosys.rosetta.rosetta.expression.ConstructorKeyValuePair
class RosettaExpressionFormatter extends AbstractRosettaFormatter2 {
@@ -114,38 +118,138 @@ class RosettaExpressionFormatter extends AbstractRosettaFormatter2 {
private def dispatch void unsafeFormatExpression(RosettaConstructorExpression expr, extension IFormattableDocument document, FormattingMode mode) {
val extension constructorGrammarAccess = rosettaCalcConstructorExpressionAccess
-
- interior(
+
+ interiorIndentWithoutCurlyBracket(
expr.regionFor.keyword(leftCurlyBracketKeyword_2)
.prepend[oneSpace]
.append[newLine],
- expr.regionFor.keyword(rightCurlyBracketKeyword_4)
- .prepend[newLine],
- [indent]
+ expr.regionFor.keyword(rightCurlyBracketKeyword_4),
+ document
)
- expr.regionFor.keywords(',').forEach[
- prepend[noSpace]
- append[newLine]
+ val rightCurlyBracketRegion = expr.regionFor.keyword(rightCurlyBracketKeyword_4)
+ rightCurlyBracketRegion.prepend [
+ if(rightCurlyBracketRegion.comesAfter("}") // case '}}'
+ || (rightCurlyBracketRegion.comesAfter(",") &&
+ rightCurlyBracketRegion.previousSemanticRegion.comesAfter("}")) // case '},}'
+ ) noSpace else newLine
+ ]
+
+ expr.regionFor.keywords(',').forEach [ valueExpr |
+ valueExpr.prepend[noSpace]
+ if (valueExpr.nextSemanticRegion.text == "}") {
+ valueExpr.append[noSpace]
+ } else {
+ valueExpr.append[newLine]
+ }
]
expr.values.forEach[
- indentInner(document)
- regionFor.keyword(':').
- prepend[noSpace]
+ regionFor.keyword(':')
+ .prepend[noSpace]
.append[oneSpace]
value.formatExpression(document, mode)
]
}
+ def comesAfter(ISemanticRegion region, String el) {
+ if (region !== null && region.previousSemanticRegion !== null) {
+ val prevRegionElement = region.previousSemanticRegion.text
+ prevRegionElement == el
+ } else
+ false
+ }
+
+ def comesBefore(ISemanticRegion region, String el) {
+ if (region !== null && region.nextSemanticRegion !== null) {
+ val nextRegionElement = region.nextSemanticRegion.text
+ nextRegionElement == el
+ } else
+ false
+ }
+
+ private def ISemanticRegion findInnermostClosingCurlyBracket(ISemanticRegion region) {
+ if (region.comesAfter("}")) // case '}}'
+ {
+ val prevRegion = region.previousSemanticRegion
+ findInnermostClosingCurlyBracket(prevRegion)
+ } else if (region.comesAfter(",") && region.previousSemanticRegion.comesAfter("}")) // case '},}')
+ {
+ val prevRegion = region.previousSemanticRegion.previousSemanticRegion
+ findInnermostClosingCurlyBracket(prevRegion)
+
+ } else {
+ region
+ }
+ }
+
+ private def boolean shouldBracketNotBeIndented(ISemanticRegion region) {
+ region.text == "}"
+ &&
+ (region.comesAfter("}") || region.comesBefore("}"))
+ ||
+ ((region.comesAfter(",") && region.previousSemanticRegion.comesAfter("}"))
+ ||
+ (region.comesBefore(",") && region.nextSemanticRegion.comesBefore("}"))
+ )
+ }
+
+ def indentInnerWithoutCurlyBracket(EObject expr, extension IFormattableDocument document) {
+ val ext = getTextRegionExt(document).previousHiddenRegion(expr)
+ expr.indentInnerWithoutCurlyBracket(ext.nextHiddenRegion, document)
+ }
+
+ def indentInnerWithoutCurlyBracket(EObject expr, IHiddenRegion firstRegion, extension IFormattableDocument document) {
+ if (expr === null || firstRegion === null) return
+ val nextRegion = getTextRegionExt(document).nextHiddenRegion(expr)
+ val end = nextRegion.previousSemanticRegion
+ set(
+ firstRegion,
+ if (shouldBracketNotBeIndented(end))
+ end.findInnermostClosingCurlyBracket.previousHiddenRegion
+ else
+ end.nextHiddenRegion,
+ [indent]
+ )
+ }
+
+ private def void surroundIndentWithoutCurlyBracket(EObject expr, extension IFormattableDocument document) {
+ if (expr === null) return
+ val objectRegion = expr.regionForEObject
+ val end = objectRegion.nextHiddenRegion.previousSemanticRegion
+
+ set(
+ objectRegion.previousHiddenRegion,
+ if (shouldBracketNotBeIndented(end))
+ end.findInnermostClosingCurlyBracket.previousHiddenRegion
+ else
+ end.nextHiddenRegion,
+ [indent]
+ )
+ }
+
+ private def void interiorIndentWithoutCurlyBracket(ISemanticRegion start, ISemanticRegion end, extension IFormattableDocument document) {
+ if (start !== null && end !== null) {
+ set(
+ start.nextHiddenRegion,
+ if (shouldBracketNotBeIndented(end))
+ end.findInnermostClosingCurlyBracket.previousHiddenRegion
+ else
+ end.previousHiddenRegion,
+ [indent]
+ )
+ }
+
+ }
+
private def dispatch void unsafeFormatExpression(ListLiteral expr, extension IFormattableDocument document, FormattingMode mode) {
expr.regionFor.keywords(',').forEach[
prepend[noSpace]
]
- interior(
+ interiorIndentWithoutCurlyBracket(
expr.regionFor.keyword('['),
expr.regionFor.keyword(']'),
- [indent]
+ document
)
formatInlineOrMultiline(document, expr, mode.singleLineIf(expr.shouldBeOnSingleLine),
@@ -175,15 +279,23 @@ class RosettaExpressionFormatter extends AbstractRosettaFormatter2 {
private def dispatch void unsafeFormatExpression(RosettaConditionalExpression expr, extension IFormattableDocument document, FormattingMode mode) {
val extension conditionalGrammarAccess = rosettaCalcConditionalExpressionAccess
+ // fix edge case where 'then' inside constructor value is not indented correctly
+ if (expr.eContainer instanceof ConstructorKeyValuePair) {
+ surround(
+ expr.regionFor.keyword(thenKeyword_3),
+ [indent]
+ )
+ }
+
expr.regionFor.keywords(ifKeyword_1, thenKeyword_3, fullElseKeyword_5_0_0).forEach[
append[oneSpace]
]
val subExprs = #[expr.^if, expr.ifthen, expr.elsethen]
- #[expr.^if, expr.ifthen].forEach[
+ #[expr.^if, expr.ifthen].forEach [
if (!(it instanceof RosettaUnaryOperation)) {
- surround(
+ surroundIndentWithoutCurlyBracket(
it,
- [indent]
+ document
)
}
]
@@ -201,7 +313,7 @@ class RosettaExpressionFormatter extends AbstractRosettaFormatter2 {
expr.regionFor.keyword(fullElseKeyword_5_0_0)
.prepend[newLine]
if (expr.eContainingFeature == ROSETTA_BINARY_OPERATION__RIGHT) {
- expr.indentInner(doc)
+ expr.indentInnerWithoutCurlyBracket(doc)
}
expr.^if.formatExpression(doc, mode.stopChain)
expr.ifthen.formatExpression(doc, mode.stopChain)
@@ -250,11 +362,13 @@ class RosettaExpressionFormatter extends AbstractRosettaFormatter2 {
}
private def dispatch void unsafeFormatExpression(RosettaSymbolReference expr, extension IFormattableDocument document, FormattingMode mode) {
+ val extension referenceCallGrammarAccess = rosettaReferenceOrFunctionCallAccess
+
if (expr.explicitArguments) {
expr.regionFor.keywords(',').forEach[
prepend[noSpace]
]
- expr.regionFor.keyword('(')
+ expr.regionFor.keyword(explicitArgumentsLeftParenthesisKeyword_0_2_0_0)
.prepend[noSpace]
formatInlineOrMultiline(document, expr, mode.singleLineIf(expr.shouldBeOnSingleLine),
@@ -269,13 +383,13 @@ class RosettaExpressionFormatter extends AbstractRosettaFormatter2 {
expr.args.forEach[formatExpression(doc, mode)]
],
[extension doc | // case: long argument list
- expr.indentInner(doc)
- interior(
+ expr.indentInnerWithoutCurlyBracket(doc)
+ interiorIndentWithoutCurlyBracket(
expr.regionFor.keyword('(')
.append[newLine],
expr.regionFor.keyword(')')
.prepend[newLine],
- [indent]
+ doc
)
expr.regionFor.keywords(',').forEach[
append[newLine]
@@ -314,7 +428,7 @@ class RosettaExpressionFormatter extends AbstractRosettaFormatter2 {
[extension doc | // case: long operation
if (!expr.left.isEmpty) {
val afterArgument = expr.left.nextHiddenRegion
- expr.indentInner(afterArgument, doc)
+ expr.indentInnerWithoutCurlyBracket(afterArgument, doc)
afterArgument
.set[newLine]
@@ -324,7 +438,7 @@ class RosettaExpressionFormatter extends AbstractRosettaFormatter2 {
false
}
if (expr.left instanceof RosettaBinaryOperation && !leftIsSameOperation) {
- expr.left.indentInner(doc)
+ expr.left.indentInnerWithoutCurlyBracket(doc)
}
expr.left.formatExpression(doc, mode.chainIf(leftIsSameOperation))
@@ -374,12 +488,12 @@ class RosettaExpressionFormatter extends AbstractRosettaFormatter2 {
}
],
[extension doc | // case: long inline function
- interior(
+ interiorIndentWithoutCurlyBracket(
left
.append[newLine],
right
.prepend[newLine],
- [indent]
+ doc
)
f.body.formatExpression(doc, mode.stopChain)
]
@@ -388,9 +502,9 @@ class RosettaExpressionFormatter extends AbstractRosettaFormatter2 {
val astRegion = f.regionForEObject
val formattableRegion = astRegion.merge(astRegion.previousHiddenRegion).merge(astRegion.nextHiddenRegion)
if (!(op instanceof ThenOperation && f.body instanceof RosettaUnaryOperation)) {
- surround(
+ surroundIndentWithoutCurlyBracket(
f.body,
- [indent]
+ document
)
}
formatInlineOrMultiline(document, astRegion, formattableRegion, mode.singleLineIf(op instanceof ThenOperation),
@@ -459,7 +573,7 @@ class RosettaExpressionFormatter extends AbstractRosettaFormatter2 {
initialArgument = initialArgument.argument
}
if (!initialArgument.isEmpty) {
- expr.indentInner(afterArgument, doc)
+ expr.indentInnerWithoutCurlyBracket(afterArgument, doc)
}
afterArgument
.set[newLine]
diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/RosettaFormatter.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/RosettaFormatter.xtend
index eb5f60b5a..a091db8bc 100644
--- a/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/RosettaFormatter.xtend
+++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/RosettaFormatter.xtend
@@ -56,6 +56,7 @@ import com.regnosys.rosetta.rosetta.TypeParameter
import com.regnosys.rosetta.rosetta.TypeCallArgument
import javax.inject.Inject
import com.regnosys.rosetta.rosetta.RosettaRule
+import com.regnosys.rosetta.rosetta.simple.Choice
class RosettaFormatter extends AbstractRosettaFormatter2 {
@@ -236,6 +237,22 @@ class RosettaFormatter extends AbstractRosettaFormatter2 {
ele.regionFor.assignment(nameAssignment_1)
.prepend[oneSpace]
}
+
+ def dispatch void format(Choice ele, extension IFormattableDocument document) {
+ ele.regionFor.keyword(choiceAccess.choiceKeyword_0)
+ .append[oneSpace]
+ ele.regionFor.keyword(':')
+ .prepend[noSpace]
+ ele.indentInner(document)
+ ele.formatDefinition(document)
+ ele.attributes.head
+ .prepend[setNewLines(1, 2, 2)]
+ .format
+ ele.attributes.tail.forEach[
+ prepend[newLine]
+ format
+ ]
+ }
def dispatch void format(Data ele, extension IFormattableDocument document) {
diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/XtextResourceFormatter.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/XtextResourceFormatter.java
index 1027d86a6..a1af59fbf 100644
--- a/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/XtextResourceFormatter.java
+++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/XtextResourceFormatter.java
@@ -1,12 +1,8 @@
package com.regnosys.rosetta.formatting2;
-import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UncheckedIOException;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.xtext.formatting2.FormatterRequest;
@@ -35,10 +31,11 @@ public class XtextResourceFormatter implements ResourceFormatterService {
private TextRegionAccessBuilder regionBuilder;
@Override
- public void formatCollection(Collection resources, ITypedPreferenceValues preferenceValues) {
+ public void formatCollection(Collection resources, ITypedPreferenceValues preferenceValues,
+ IFormattedResourceAcceptor acceptor) {
resources.stream().forEach(resource -> {
if (resource instanceof XtextResource) {
- formatXtextResource((XtextResource) resource, preferenceValues);
+ formatXtextResource((XtextResource) resource, preferenceValues, acceptor);
} else {
LOGGER.debug("Resource is not of type XtextResource and will be skipped: " + resource.getURI());
@@ -47,7 +44,15 @@ public void formatCollection(Collection resources, ITypedPreferenceVal
}
@Override
- public void formatXtextResource(XtextResource resource, ITypedPreferenceValues preferenceValues) {
+ public void formatXtextResource(XtextResource resource, ITypedPreferenceValues preferenceValues,
+ IFormattedResourceAcceptor acceptor) {
+ if (!resource.getAllContents().hasNext()) {
+ LOGGER.info("Resource " + resource.getURI() + " is empty.");
+ return;
+ }
+
+ LOGGER.info("Formatting file at location " + resource.getURI());
+
// setup request and formatter
FormatterRequest req = formatterRequestProvider.get();
req.setPreferences(preferenceValues);
@@ -57,20 +62,20 @@ public void formatXtextResource(XtextResource resource, ITypedPreferenceValues p
req.setTextRegionAccess(regionAccess);
// list contains all the replacements which should be applied to resource
- List replacements = formatter.format(req);
+ List replacements;
+ try {
+ replacements = formatter.format(req); // throws exception
+ } catch (RuntimeException e) {
+ LOGGER.error("RuntimeException in " + resource.getURI() + ": " + e.getMessage(), e);
+ replacements = new ArrayList<>();
+ }
// formatting using TextRegionRewriter
ITextRegionRewriter regionRewriter = regionAccess.getRewriter();
String formattedString = regionRewriter.renderToString(regionAccess.regionForDocument(), replacements);
- // With the formatted text, update the resource
- InputStream resultStream = new ByteArrayInputStream(formattedString.getBytes(StandardCharsets.UTF_8));
- resource.unload();
- try {
- resource.load(resultStream, null);
- } catch (IOException e) {
- throw new UncheckedIOException(
- "Since the resource is an in-memory string, this exception is not expected to be ever thrown.", e);
- }
+ // Perform handler operation
+ acceptor.accept(resource, formattedString);
}
+
}
diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/serialization/RosettaTransientValueService.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/serialization/RosettaTransientValueService.java
index f6cd69328..64f6d8895 100644
--- a/rosetta-lang/src/main/java/com/regnosys/rosetta/serialization/RosettaTransientValueService.java
+++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/serialization/RosettaTransientValueService.java
@@ -17,6 +17,7 @@
package com.regnosys.rosetta.serialization;
import java.util.List;
+import java.util.Set;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
@@ -24,29 +25,43 @@
import com.regnosys.rosetta.rosetta.expression.ExpressionPackage;
import com.regnosys.rosetta.rosetta.expression.RosettaExpression;
+import com.regnosys.rosetta.rosetta.simple.SimplePackage;
public class RosettaTransientValueService extends DefaultTransientValueService {
- private EStructuralFeature generatedInputWasSetFeature = ExpressionPackage.eINSTANCE.getHasGeneratedInput_GeneratedInputWasSet();
- private EStructuralFeature implicitVariableIsInContextFeature = ExpressionPackage.eINSTANCE.getRosettaSymbolReference_ImplicitVariableIsInContext();
-
+ private final Set ignoredFeatures;
+
+ public RosettaTransientValueService() {
+ EStructuralFeature generatedInputWasSetFeature = ExpressionPackage.eINSTANCE
+ .getHasGeneratedInput_GeneratedInputWasSet();
+ EStructuralFeature implicitVariableIsInContextFeature = ExpressionPackage.eINSTANCE
+ .getRosettaSymbolReference_ImplicitVariableIsInContext();
+ EStructuralFeature hardcodedConditionFeature = SimplePackage.eINSTANCE.getChoice__hardcodedConditions();
+ EStructuralFeature hardcodedNameFeature = SimplePackage.eINSTANCE.getChoiceOption__hardcodedName();
+ EStructuralFeature hardcodedCardinalityFeature = SimplePackage.eINSTANCE
+ .getChoiceOption__hardcodedCardinality();
+ ignoredFeatures = Set.of(generatedInputWasSetFeature, implicitVariableIsInContextFeature,
+ hardcodedConditionFeature, hardcodedNameFeature, hardcodedCardinalityFeature);
+
+ }
+
@Override
public boolean isCheckElementsIndividually(EObject owner, EStructuralFeature feature) {
return true;
}
-
+
@Override
public boolean isTransient(EObject owner, EStructuralFeature feature, int index) {
if (super.isTransient(owner, feature, index)) {
return true;
}
- if (feature.equals(generatedInputWasSetFeature) || feature.equals(implicitVariableIsInContextFeature)) {
+ if (ignoredFeatures.contains(feature)) {
return true;
}
Object value = owner.eGet(feature);
if (index >= 0 && value instanceof List>) {
- value = ((List>)value).get(index);
+ value = ((List>) value).get(index);
}
- if (value instanceof RosettaExpression && ((RosettaExpression)value).isGenerated()) {
+ if (value instanceof RosettaExpression && ((RosettaExpression) value).isGenerated()) {
return true;
}
return false;
diff --git a/rosetta-maven-plugin/src/main/java/com/regnosys/rosetta/maven/ResourceFormatterMojo.java b/rosetta-maven-plugin/src/main/java/com/regnosys/rosetta/maven/ResourceFormatterMojo.java
new file mode 100644
index 000000000..c6b30f416
--- /dev/null
+++ b/rosetta-maven-plugin/src/main/java/com/regnosys/rosetta/maven/ResourceFormatterMojo.java
@@ -0,0 +1,115 @@
+package com.regnosys.rosetta.maven;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.lsp4j.FormattingOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Injector;
+import com.regnosys.rosetta.RosettaStandaloneSetup;
+import com.regnosys.rosetta.formatting2.FormattingOptionsAdaptor;
+import com.regnosys.rosetta.formatting2.ResourceFormatterService;
+
+/**
+ *
+ * Formatter Plugin: A Maven plugin to help with the formatting of resources.
+ *
+ *
+ *
+ * Given a path to a directory holding {@code .rosetta} resources, it formats
+ * the files according to set formatting rules. Additionally, you can specify a
+ * custom configuration file for formatting options using the
+ * {@code formattingOptionsPath} parameter. If the {@code formattingOptionsPath}
+ * is not provided, the plugin will use default formatting options.
+ *
+ *
+ *
+ *
+ * To run the goal:
+ *
+ * - {@code mvn com.regnosys.rosetta:rosetta-maven-plugin:version:format -Dpath="path/to/directory"}
+ * - Optionally, provide a custom formatting options file using
+ * {@code -DformattingOptionsPath="path/to/formattingOptions.json"}
+ *
+ *
+ *
+ *
+ * Example with both parameters:
+ *
+ * - {@code mvn com.regnosys.rosetta:rosetta-maven-plugin:version:format -Dpath="path/to/directory" -DformattingOptionsPath="path/to/formattingOptions.json"}
+ *
+ *
+ */
+@Mojo(name = "format")
+public class ResourceFormatterMojo extends AbstractMojo {
+ private static Logger LOGGER = LoggerFactory.getLogger(ResourceFormatterMojo.class);
+
+ /**
+ * Path to the directory of files to be formatted
+ */
+ @Parameter(property = "path", required = true)
+ private String path;
+
+ /**
+ * Path to the .json file containing formatting options
+ */
+ @Parameter(property = "formattingOptionsPath", required = false)
+ private String formattingOptionsPath;
+
+ @Inject
+ private FormattingOptionsAdaptor formattingOptionsAdapter;
+
+ @Override
+ public void execute() throws MojoExecutionException, MojoFailureException {
+ Path directory = Paths.get(path);
+ LOGGER.info("Mojo running on path:" + directory.toString());
+
+ FormattingOptions formattingOptions = null;
+ try {
+ formattingOptions = formattingOptionsAdapter.readFormattingOptions(formattingOptionsPath);
+ } catch (IOException e) {
+ LOGGER.error("Config file not found.", e);
+ }
+
+ Injector inj = new RosettaStandaloneSetup().createInjectorAndDoEMFRegistration();
+ ResourceSet resourceSet = inj.getInstance(ResourceSet.class);
+ ResourceFormatterService formatterService = inj.getInstance(ResourceFormatterService.class);
+
+ List resources;
+ try {
+ // Find all .rosetta files in the directory and load them from disk
+ resources = Files.walk(directory).filter(path -> path.toString().endsWith(".rosetta"))
+ .map(file -> resourceSet.getResource(URI.createFileURI(file.toString()), true))
+ .collect(Collectors.toList());
+ } catch (IOException e) {
+ throw new MojoFailureException("Error processing files: " + e.getMessage(), e);
+ }
+ // format resources
+ formatterService.formatCollection(resources, formattingOptionsAdapter.createPreferences(formattingOptions),
+ (resource, formattedText) -> {
+ Path resourcePath = Path.of(resource.getURI().toFileString());
+ try {
+ Files.writeString(resourcePath, formattedText);
+ LOGGER.info("Content written to file: " + resourcePath);
+ } catch (IOException e) {
+ LOGGER.error("Error writing to file.", e);
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/rosetta-runtime/src/main/resources/default-formatting-options.json b/rosetta-runtime/src/main/resources/default-formatting-options.json
new file mode 100644
index 000000000..aadbd833e
--- /dev/null
+++ b/rosetta-runtime/src/main/resources/default-formatting-options.json
@@ -0,0 +1,6 @@
+{
+ "tabSize":4,
+ "insertSpaces":true,
+ "maxLineWidth":92,
+ "conditionalMaxLineWidth":70
+}
\ No newline at end of file
diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/ResourceFormatterServiceTest.java b/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/ResourceFormatterServiceTest.java
index 9c38fcdea..0a5076583 100644
--- a/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/ResourceFormatterServiceTest.java
+++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/ResourceFormatterServiceTest.java
@@ -20,7 +20,6 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-import java.util.stream.Collectors;
import com.google.common.io.Resources;
import com.regnosys.rosetta.tests.RosettaInjectorProvider;
@@ -32,14 +31,13 @@ public class ResourceFormatterServiceTest {
ResourceFormatterService formatterService;
@Inject
Provider resourceSetProvider;
- @Inject
- ISerializer serializer;
private void testFormatting(Collection inputUrls, Collection expectedUrls)
throws IOException, URISyntaxException {
ResourceSet resourceSet = resourceSetProvider.get();
List resources = new ArrayList<>();
- List expected = new ArrayList<>();
+ List formattedText = new ArrayList<>();
+ List expectedText = new ArrayList<>();
for (String url : inputUrls) {
Resource resource = resourceSet.getResource(URI.createURI(Resources.getResource(url).toString()), true);
@@ -47,15 +45,14 @@ private void testFormatting(Collection inputUrls, Collection exp
}
for (String url : expectedUrls) {
- expected.add(Files.readString(Path.of(Resources.getResource(url).toURI())));
+ expectedText.add(Files.readString(Path.of(Resources.getResource(url).toURI())));
}
- formatterService.formatCollection(resources);
-
- List result = resources.stream().map(resource -> serializer.serialize(resource.getContents().get(0)))
- .collect(Collectors.toList());
+ formatterService.formatCollection(resources, (resource, formattedContent) -> {
+ formattedText.add(formattedContent); // Collect formatted content for assertions
+ });
- Assertions.assertIterableEquals(expected, result);
+ Assertions.assertIterableEquals(expectedText, formattedText);
}
@Test
@@ -72,4 +69,11 @@ void formatMultipleDocuments() throws IOException, URISyntaxException {
List.of("formatting-test/expected/typeAlias.rosetta",
"formatting-test/expected/typeAliasWithDocumentation.rosetta"));
}
+
+ @Test
+ void formatNestedConstructor() throws IOException, URISyntaxException {
+ testFormatting(List.of("formatting-test/input/nestedConstructor.rosetta"),
+ List.of("formatting-test/expected/nestedConstructor.rosetta"));
+ }
+
}
diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/RosettaExpressionFormattingTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/RosettaExpressionFormattingTest.xtend
index 32fa3fb31..8761c7826 100644
--- a/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/RosettaExpressionFormattingTest.xtend
+++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/RosettaExpressionFormattingTest.xtend
@@ -63,10 +63,10 @@ class RosettaExpressionFormattingTest {
SomeType {
attr1: "Some expression",
attr2: foo
- extract
- if True
- then ["This is a looong", "expression"]
- else 42,
+ extract
+ if True
+ then ["This is a looong", "expression"]
+ else 42,
}
'''
}
@@ -87,15 +87,98 @@ class RosettaExpressionFormattingTest {
SomeType {
attr1: "Some expression",
attr2: foo
- extract
- if True
- then ["This is a looong", "expression"]
- else 42,
+ extract
+ if True
+ then ["This is a looong", "expression"]
+ else 42,
...
}
'''
}
+ @Test
+ def void testConstructorFormat3() {
+ '''
+ SomeType {
+ attr1: "Some expression",
+ attr2: Foo {
+ bar: True
+ },
+ }
+ ''' ->
+ '''
+ SomeType {
+ attr1: "Some expression",
+ attr2: Foo {
+ bar: True
+ },}
+ '''
+ }
+
+ @Test
+ def void testConstructorNestedWithBooleanFormat() {
+ '''
+ Constr1 {
+ attr1: if True
+ then False,
+ attr2: if False
+ then Constr2 {
+ attr11: Constr3 {
+ attr111: 42
+ }}
+ }
+ ''' ->
+ '''
+ Constr1 {
+ attr1: if True then False,
+ attr2: if False
+ then Constr2 {
+ attr11: Constr3 {
+ attr111: 42
+ }}}
+ '''
+ }
+
+ @Test
+ def void testCollapsingBracketsDeepNested() {
+ '''
+ Constr1 {
+ attr1: if True then False,
+ attr2: if False
+ then 42 extract Constr2 {
+ attr11: Constr3 {
+ attr111: item
+ }}}
+ '''->'''
+ Constr1 {
+ attr1: if True then False,
+ attr2: if False
+ then 42
+ extract
+ Constr2 {
+ attr11: Constr3 {
+ attr111: item
+ }}}
+ '''
+ }
+
+ @Test
+ def void testConstructorNestedInUnaryOperation() {
+ '''
+ el1
+ extract
+ Constr1 {
+ attr1: val1
+ }
+ ''' -> '''
+ el1
+ extract
+ Constr1 {
+ attr1: val1
+ }
+ '''
+ }
+
@Test
def void testOperationChainingFormat1() {
'''
@@ -761,4 +844,26 @@ class RosettaExpressionFormattingTest {
(["This", "is", "a", "loooooooooooooooooooooooong", "list"] count > 10)
'''
}
+
+ @Test
+ def void testFunctionCallInParenthesis() {
+ '''
+ (SomeFunc
+ (
+ ))
+ ''' -> '''
+ (SomeFunc())
+ '''
+ }
+
+ @Test
+ def void testConditionalInParenthesis() {
+ '''
+ (if True
+ then 10)
+ ''' -> '''
+ (if True then 10)
+ '''
+ }
+
}
\ No newline at end of file
diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/RosettaFormattingTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/RosettaFormattingTest.xtend
index c38bc8902..9f1289441 100644
--- a/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/RosettaFormattingTest.xtend
+++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/RosettaFormattingTest.xtend
@@ -588,4 +588,25 @@ class RosettaFormattingTest {
add out -> other: [in1, in1, in1, in1]
'''
}
+
+ @Test
+ def void testChoice() {
+ '''
+ namespace "test"
+ version "test"
+ choice Test:
+ A
+
+ B
+
+ ''' -> '''
+ namespace "test"
+ version "test"
+
+ choice Test:
+ A
+ B
+ '''
+ }
+
}
diff --git a/rosetta-testing/src/test/resources/formatting-test/expected/nestedConstructor.rosetta b/rosetta-testing/src/test/resources/formatting-test/expected/nestedConstructor.rosetta
new file mode 100644
index 000000000..3209e96fd
--- /dev/null
+++ b/rosetta-testing/src/test/resources/formatting-test/expected/nestedConstructor.rosetta
@@ -0,0 +1,11 @@
+namespace test
+
+func MyOtherFunc:
+ output:
+ result Foo (1..1)
+
+ set result:
+ Foo {
+ bar: Bar {
+ attr: "bar attr"
+ }}
diff --git a/rosetta-testing/src/test/resources/formatting-test/input/nestedConstructor.rosetta b/rosetta-testing/src/test/resources/formatting-test/input/nestedConstructor.rosetta
new file mode 100644
index 000000000..5cafd7e54
--- /dev/null
+++ b/rosetta-testing/src/test/resources/formatting-test/input/nestedConstructor.rosetta
@@ -0,0 +1,12 @@
+namespace test
+
+func MyOtherFunc:
+ output:
+ result Foo (1..1)
+
+ set result:
+ Foo {
+ bar: Bar {
+ attr: "bar attr"
+ }
+ }
diff --git a/rosetta-testing/src/test/resources/formatting-test/input/onlyExists.rosetta b/rosetta-testing/src/test/resources/formatting-test/input/onlyExists.rosetta
new file mode 100644
index 000000000..775ab13ea
--- /dev/null
+++ b/rosetta-testing/src/test/resources/formatting-test/input/onlyExists.rosetta
@@ -0,0 +1,8 @@
+namespace test
+
+func MyFunc:
+ output:
+ result boolean (1..1)
+
+ set result:
+ (foo -> bar) only exists
diff --git a/rosetta-tools/src/main/java/com/regnosys/rosetta/tools/ResourceFormattingTool.java b/rosetta-tools/src/main/java/com/regnosys/rosetta/tools/ResourceFormattingTool.java
index 3981db8f8..605881e0b 100644
--- a/rosetta-tools/src/main/java/com/regnosys/rosetta/tools/ResourceFormattingTool.java
+++ b/rosetta-tools/src/main/java/com/regnosys/rosetta/tools/ResourceFormattingTool.java
@@ -7,16 +7,19 @@
import java.util.List;
import java.util.stream.Collectors;
+import javax.inject.Inject;
+
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.lsp4j.FormattingOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.Injector;
import com.regnosys.rosetta.RosettaStandaloneSetup;
+import com.regnosys.rosetta.formatting2.FormattingOptionsAdaptor;
import com.regnosys.rosetta.formatting2.ResourceFormatterService;
-import com.regnosys.rosetta.formatting2.XtextResourceFormatter;
/**
* A command-line tool for formatting `.rosetta` files in a specified directory.
@@ -36,45 +39,68 @@
*
*/
public class ResourceFormattingTool {
+ @Inject
+ private static FormattingOptionsAdaptor formattingOptionsAdapter;
+
private static Logger LOGGER = LoggerFactory.getLogger(ResourceFormattingTool.class);
public static void main(String[] args) {
+ int maxArgs = 2;
+
if (args.length == 0) {
- System.out.println("Please provide the directory path as an argument.");
- System.exit(1);
+ exitProgram("Please provide the directory path as an argument.");
+ }
+
+ if (args.length > maxArgs) {
+ exitProgram("Too many arguments. Please provide maximum " + maxArgs + " arguments.");
}
Path directory = Paths.get(args[0]);
if (!Files.isDirectory(directory)) {
- System.out.println("The provided path is not a valid directory.");
- System.exit(1);
+ exitProgram("The provided path is not a valid directory.");
+ }
+
+ // check if optional parameter was given. If not use default value
+ FormattingOptions formattingOptions = null;
+ if(args.length > 1) {
+ String formattingOptionsPath = args[1];
+ try {
+ formattingOptions = formattingOptionsAdapter.readFormattingOptions(formattingOptionsPath);
+ } catch (IOException e) {
+ LOGGER.error("Config file not found.", e);
+ }
}
Injector inj = new RosettaStandaloneSetup().createInjectorAndDoEMFRegistration();
ResourceSet resourceSet = inj.getInstance(ResourceSet.class);
ResourceFormatterService formatterService = inj.getInstance(ResourceFormatterService.class);
+ List resources = null;
try {
// Find all .rosetta files in the directory and load them from disk
- List resources = Files.walk(directory)
+ resources = Files.walk(directory)
.filter(path -> path.toString().endsWith(".rosetta"))
.map(file -> resourceSet.getResource(URI.createFileURI(file.toString()), true))
.collect(Collectors.toList());
- // format resources
- formatterService.formatCollection(resources, null);
-
- // save each resource
- resources.forEach(resource -> {
- try {
- resource.save(null);
- LOGGER.info("Successfully formatted and saved file at location " + resource.getURI());
- } catch (IOException e) {
- LOGGER.error("Error saving file at location " + resource.getURI() + ": "+ e.getMessage());
- }
- });
} catch (IOException e) {
LOGGER.error("Error processing files: " + e.getMessage());
}
+
+ formatterService.formatCollection(resources, formattingOptionsAdapter.createPreferences(formattingOptions),
+ (resource, formattedText) -> {
+ Path resourcePath = Path.of(resource.getURI().toFileString());
+ try {
+ Files.writeString(resourcePath, formattedText);
+ LOGGER.info("Content written to file: " + resourcePath);
+ } catch (IOException e) {
+ LOGGER.error("Error writing to file.", e);
+ }
+ });
+ }
+
+ private static void exitProgram(String msg) {
+ System.out.println(msg);
+ System.exit(1);
}
}