-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Automating Formatting of Rune Code (#859)
* 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
- Loading branch information
1 parent
fb297a7
commit 8827ec7
Showing
10 changed files
with
340 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
70 changes: 70 additions & 0 deletions
70
rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/ResourceFormatterService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package com.regnosys.rosetta.formatting2; | ||
|
||
import java.util.Collection; | ||
|
||
import org.eclipse.emf.ecore.resource.Resource; | ||
import org.eclipse.xtext.preferences.ITypedPreferenceValues; | ||
import org.eclipse.xtext.resource.XtextResource; | ||
|
||
public interface ResourceFormatterService { | ||
|
||
/** | ||
* Formats each {@link XtextResource} in the provided collection in-memory. | ||
* <p> | ||
* 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. | ||
* </p> | ||
* | ||
* @param resources a collection of {@link XtextResource} objects to be formatted | ||
*/ | ||
default void formatCollection(Collection<Resource> resources) { | ||
formatCollection(resources, null); | ||
} | ||
|
||
/** | ||
* Formats the given {@link XtextResource} in-memory. | ||
* <p> | ||
* 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. | ||
* </p> | ||
* | ||
* @param resources the {@link XtextResource} to format | ||
* @param preferenceValues an {@link ITypedPreferenceValues} object containing formatting preferences, | ||
* or {@code null} if no preferences are specified | ||
*/ | ||
default void formatXtextResource(XtextResource resource) { | ||
formatXtextResource(resource, null); | ||
} | ||
|
||
/** | ||
* Formats each {@link XtextResource} in the provided collection in-memory, with specified formatting preferences. | ||
* <p> | ||
* 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}. | ||
* </p> | ||
* | ||
* @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 | ||
*/ | ||
void formatCollection(Collection<Resource> resources, ITypedPreferenceValues preferenceValues); | ||
|
||
/** | ||
* Formats the given {@link XtextResource} in-memory. | ||
* <p> | ||
* 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}. | ||
* </p> | ||
* | ||
* @param resource the {@link XtextResource} to format | ||
*/ | ||
void formatXtextResource(XtextResource resource, ITypedPreferenceValues preferenceValues); | ||
} |
76 changes: 76 additions & 0 deletions
76
rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/XtextResourceFormatter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package com.regnosys.rosetta.formatting2; | ||
|
||
import java.nio.charset.StandardCharsets; | ||
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; | ||
import org.eclipse.xtext.formatting2.IFormatter2; | ||
import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess; | ||
import org.eclipse.xtext.formatting2.regionaccess.ITextRegionRewriter; | ||
import org.eclipse.xtext.formatting2.regionaccess.ITextReplacement; | ||
import org.eclipse.xtext.formatting2.regionaccess.TextRegionAccessBuilder; | ||
import org.eclipse.xtext.preferences.ITypedPreferenceValues; | ||
import org.eclipse.xtext.resource.XtextResource; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import javax.inject.Inject; | ||
import javax.inject.Provider; | ||
|
||
public class XtextResourceFormatter implements ResourceFormatterService { | ||
private static Logger LOGGER = LoggerFactory.getLogger(XtextResourceFormatter.class); | ||
@Inject | ||
private Provider<FormatterRequest> formatterRequestProvider; | ||
|
||
@Inject | ||
private Provider<IFormatter2> iFormatter2Provider; | ||
|
||
@Inject | ||
private TextRegionAccessBuilder regionBuilder; | ||
|
||
@Override | ||
public void formatCollection(Collection<Resource> resources, ITypedPreferenceValues preferenceValues) { | ||
resources.stream().forEach(resource -> { | ||
if (resource instanceof XtextResource) { | ||
formatXtextResource((XtextResource) resource, preferenceValues); | ||
|
||
} else { | ||
LOGGER.debug("Resource is not of type XtextResource and will be skipped: " + resource.getURI()); | ||
} | ||
}); | ||
} | ||
|
||
@Override | ||
public void formatXtextResource(XtextResource resource, ITypedPreferenceValues preferenceValues) { | ||
// setup request and formatter | ||
FormatterRequest req = formatterRequestProvider.get(); | ||
req.setPreferences(preferenceValues); | ||
IFormatter2 formatter = iFormatter2Provider.get(); | ||
|
||
ITextRegionAccess regionAccess = regionBuilder.forNodeModel(resource).create(); | ||
req.setTextRegionAccess(regionAccess); | ||
|
||
// list contains all the replacements which should be applied to resource | ||
List<ITextReplacement> replacements = formatter.format(req); | ||
|
||
// 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); | ||
} | ||
} | ||
} |
75 changes: 75 additions & 0 deletions
75
...-testing/src/test/java/com/regnosys/rosetta/formatting2/ResourceFormatterServiceTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package com.regnosys.rosetta.formatting2; | ||
|
||
import javax.inject.Inject; | ||
import javax.inject.Provider; | ||
|
||
import org.eclipse.emf.common.util.URI; | ||
import org.eclipse.emf.ecore.resource.Resource; | ||
import org.eclipse.emf.ecore.resource.ResourceSet; | ||
import org.eclipse.xtext.serializer.ISerializer; | ||
import org.eclipse.xtext.testing.InjectWith; | ||
import org.eclipse.xtext.testing.extensions.InjectionExtension; | ||
import org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
|
||
import java.io.IOException; | ||
import java.net.URISyntaxException; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
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; | ||
|
||
@ExtendWith(InjectionExtension.class) | ||
@InjectWith(RosettaInjectorProvider.class) | ||
public class ResourceFormatterServiceTest { | ||
@Inject | ||
ResourceFormatterService formatterService; | ||
@Inject | ||
Provider<ResourceSet> resourceSetProvider; | ||
@Inject | ||
ISerializer serializer; | ||
|
||
private void testFormatting(Collection<String> inputUrls, Collection<String> expectedUrls) | ||
throws IOException, URISyntaxException { | ||
ResourceSet resourceSet = resourceSetProvider.get(); | ||
List<Resource> resources = new ArrayList<>(); | ||
List<String> expected = new ArrayList<>(); | ||
|
||
for (String url : inputUrls) { | ||
Resource resource = resourceSet.getResource(URI.createURI(Resources.getResource(url).toString()), true); | ||
resources.add(resource); | ||
} | ||
|
||
for (String url : expectedUrls) { | ||
expected.add(Files.readString(Path.of(Resources.getResource(url).toURI()))); | ||
} | ||
|
||
formatterService.formatCollection(resources); | ||
|
||
List<String> result = resources.stream().map(resource -> serializer.serialize(resource.getContents().get(0))) | ||
.collect(Collectors.toList()); | ||
|
||
Assertions.assertIterableEquals(expected, result); | ||
} | ||
|
||
@Test | ||
void formatSingleDocument() throws IOException, URISyntaxException { | ||
testFormatting(List.of("formatting-test/input/typeAlias.rosetta"), | ||
List.of("formatting-test/expected/typeAlias.rosetta")); | ||
} | ||
|
||
@Test | ||
void formatMultipleDocuments() throws IOException, URISyntaxException { | ||
testFormatting( | ||
List.of("formatting-test/input/typeAlias.rosetta", | ||
"formatting-test/input/typeAliasWithDocumentation.rosetta"), | ||
List.of("formatting-test/expected/typeAlias.rosetta", | ||
"formatting-test/expected/typeAliasWithDocumentation.rosetta")); | ||
} | ||
} |
3 changes: 3 additions & 0 deletions
3
rosetta-testing/src/test/resources/formatting-test/expected/typeAlias.rosetta
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
namespace test | ||
|
||
typeAlias maxNBoundedNumber(n int, max number): number(digits: n, max: max) |
7 changes: 7 additions & 0 deletions
7
...ta-testing/src/test/resources/formatting-test/expected/typeAliasWithDocumentation.rosetta
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
namespace test | ||
|
||
typeAlias maxNBoundedNumber( | ||
n int | ||
, max number <"The maximum bound on this number. If absent, this number is unbounded from above."> | ||
): <"A bounded decimal number with N maximum number of digits."> | ||
number(digits: n, max: max) |
7 changes: 7 additions & 0 deletions
7
rosetta-testing/src/test/resources/formatting-test/input/typeAlias.rosetta
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
namespace test | ||
|
||
|
||
typeAlias maxNBoundedNumber | ||
( n int ,max number) | ||
: | ||
number ( digits : n , max: max ) |
8 changes: 8 additions & 0 deletions
8
rosetta-testing/src/test/resources/formatting-test/input/typeAliasWithDocumentation.rosetta
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace test | ||
|
||
|
||
typeAlias maxNBoundedNumber | ||
( n int | ||
,max number <"The maximum bound on this number. If absent, this number is unbounded from above.">) | ||
: <"A bounded decimal number with N maximum number of digits."> | ||
number ( digits : n , max: max ) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
80 changes: 80 additions & 0 deletions
80
rosetta-tools/src/main/java/com/regnosys/rosetta/tools/ResourceFormattingTool.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package com.regnosys.rosetta.tools; | ||
|
||
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 org.eclipse.emf.common.util.URI; | ||
import org.eclipse.emf.ecore.resource.Resource; | ||
import org.eclipse.emf.ecore.resource.ResourceSet; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import com.google.inject.Injector; | ||
import com.regnosys.rosetta.RosettaStandaloneSetup; | ||
import com.regnosys.rosetta.formatting2.ResourceFormatterService; | ||
import com.regnosys.rosetta.formatting2.XtextResourceFormatter; | ||
|
||
/** | ||
* A command-line tool for formatting `.rosetta` files in a specified directory. | ||
* <p> | ||
* This tool uses the {@link ResourceFormatterService} to apply consistent formatting to each | ||
* `.rosetta` file in the provided directory. It loads each file as a resource, applies | ||
* formatting in-place, and saves the modified file back to disk. The tool can be run with a | ||
* single directory path argument, which is used to locate `.rosetta` files. | ||
* </p> | ||
* | ||
* <h2>Usage:</h2> | ||
* <pre> | ||
* java ResourceFormattingTool /path/to/directory | ||
* </pre> | ||
* <p> | ||
* If no valid directory path is provided as an argument, the program will exit with an error message. | ||
* </p> | ||
*/ | ||
public class ResourceFormattingTool { | ||
private static Logger LOGGER = LoggerFactory.getLogger(ResourceFormattingTool.class); | ||
|
||
public static void main(String[] args) { | ||
if (args.length == 0) { | ||
System.out.println("Please provide the directory path as an argument."); | ||
System.exit(1); | ||
} | ||
|
||
Path directory = Paths.get(args[0]); | ||
if (!Files.isDirectory(directory)) { | ||
System.out.println("The provided path is not a valid directory."); | ||
System.exit(1); | ||
} | ||
|
||
Injector inj = new RosettaStandaloneSetup().createInjectorAndDoEMFRegistration(); | ||
ResourceSet resourceSet = inj.getInstance(ResourceSet.class); | ||
ResourceFormatterService formatterService = inj.getInstance(ResourceFormatterService.class); | ||
|
||
try { | ||
// Find all .rosetta files in the directory and load them from disk | ||
List<Resource> 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()); | ||
} | ||
} | ||
} |