Skip to content

Commit

Permalink
Formatter Maven plugin (#864)
Browse files Browse the repository at this point in the history
* Created formatter interface

* Implement interface + first Test

* Fix formatting test + implementation

* Enhanced test suite

* Implement suggested changes

* Improved interface methods/exception handling and added logging

* Added formatting tool method

* Add dependencies for logging in tools project

* Improve implementation of command line tool

* Formatting java code

* Made formatting happen in-memory

* Update logs when resource saved

* Fixed logger class name

* Starting code for formatter plugin

* Incorporate 'format' goal in existing plugin

* Cleanup files

* Add test cases for edge cases and unusual behavior

* Update code to deal with runtime exceptions

* Fix formatting errors with Choice and parenthesis

* Implement suggested changes

* Fix comment

* Implement suggestion

* Fix only exists serialization issue

* Cleanup test

* Update plugin to also use user-given formatting options

* Update plugin to use formatting options

* Update formatting strategy to avoid serialization

* Add handler support to service for processing formatted content

* Implemented suggested changes

* Update cmd tool + cleanup

* Format closing brackets to collapse in nested constructors

* Introduce FormattingOptionsAdaptor for shared FormattingOptions logic

* Formatted collapsable closing brackets in nested constructors

* Fix collapsing closing brackets logic

* Add endpoint for default formatting options

* Implement suggested changes

* Fix failing test

* Cleanup code
  • Loading branch information
maria77102 authored Dec 11, 2024
1 parent f45ca93 commit f1f8f37
Show file tree
Hide file tree
Showing 24 changed files with 807 additions and 176 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<IFormatter2> 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<TextEdit> format(XtextResource resource, Document document, int offset, int length,
FormattingOptions options) {
List<TextEdit> result = new ArrayList<>();
if (this.formatter2Provider != null) {
ITypedPreferenceValues preferences = createPreferences(options);
ITypedPreferenceValues preferences = formattingOptionsAdapter.createPreferences(options);
List<ITextReplacement> replacements = format2(resource, new TextRegion(offset, length), preferences);
for (ITextReplacement r : replacements) {
result.add(toTextEdit(document, r.getReplacementText(), r.getOffset(), r.getLength()));
Expand Down
Original file line number Diff line number Diff line change
@@ -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<FormattingOptions> getDefaultFormattingOptions();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -170,4 +175,14 @@ protected SemanticTokens semanticTokensRange(SemanticTokensRangeParams params, C
public CompletableFuture<SemanticTokens> semanticTokensRange(SemanticTokensRangeParams params) {
return this.getRequestManager().runRead((cancelIndicator) -> this.semanticTokensRange(params, cancelIndicator));
}

@Override
public CompletableFuture<FormattingOptions> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
],
[
Expand Down Expand Up @@ -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
],
[
Expand Down
1 change: 1 addition & 0 deletions rosetta-lang/model/RosettaExpression.xcore
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ class JoinOperation extends RosettaBinaryOperation {

class RosettaOnlyExistsExpression extends RosettaExpression {
contains RosettaExpression[] args
boolean hasParentheses
}

/**
Expand Down
4 changes: 4 additions & 0 deletions rosetta-lang/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.lsp4j</groupId>
<artifactId>org.eclipse.lsp4j</artifactId>
</dependency>
<!-- This dependency is only necessary to make it work in Eclipse. TODO:
remove this and figure out a better way. -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, Object> map = null;
map = objectMapper.readValue(resourceStream, Map.class);

// Create a FormattingOptions object
FormattingOptions formattingOptions = new FormattingOptions();

// Populate the FormattingOptions object
for (Map.Entry<String, Object> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* 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.
* </p>
*/
@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);
}
Loading

0 comments on commit f1f8f37

Please sign in to comment.