From a514456771e018aa7570134ddaa1a354ce96d50a Mon Sep 17 00:00:00 2001
From: Arne Deutsch
Date: Fri, 11 Sep 2020 10:49:36 +0200
Subject: [PATCH] [eclipse/xtext#1697] Abstract super class for java based
formatters.
Remove API restrictions.
Testing java based formatter.
Signed-off-by: Arne Deutsch
---
.../formatting2/internal/JavaFormatter.java | 31 ++++
.../internal/JavaFormatterTest.java | 34 ++++
.../internal/JavaFormatterTestHelper.java | 34 ++++
org.eclipse.xtext/META-INF/MANIFEST.MF | 27 +--
.../formatting2/AbstractJavaFormatter.java | 156 ++++++++++++++++++
5 files changed, 257 insertions(+), 25 deletions(-)
create mode 100644 org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/JavaFormatter.java
create mode 100644 org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/JavaFormatterTest.java
create mode 100644 org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/JavaFormatterTestHelper.java
create mode 100644 org.eclipse.xtext/src/org/eclipse/xtext/formatting2/AbstractJavaFormatter.java
diff --git a/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/JavaFormatter.java b/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/JavaFormatter.java
new file mode 100644
index 000000000..8ea829704
--- /dev/null
+++ b/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/JavaFormatter.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2020 itemis AG (http://www.itemis.eu) and others.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.xtext.formatting2.internal;
+
+import org.eclipse.xtext.formatting2.AbstractJavaFormatter;
+import org.eclipse.xtext.formatting2.IFormattableDocument;
+import org.eclipse.xtext.formatting2.internal.formattertestlanguage.IDList;
+import org.eclipse.xtext.formatting2.internal.services.FormatterTestLanguageGrammarAccess;
+
+import com.google.inject.Inject;
+
+/**
+ * @author Arne Deutsch - Initial contribution and API
+ */
+public class JavaFormatter extends AbstractJavaFormatter {
+
+ @Inject
+ FormatterTestLanguageGrammarAccess grammar;
+
+ protected void format(IDList list, IFormattableDocument doc) {
+ doc.prepend(regionFor(list).keyword("idlist"), it -> it.noSpace());
+ regionFor(list).ruleCallsTo(grammar.getIDRule()).forEach(region -> doc.prepend(region, it -> it.oneSpace()));
+ }
+
+}
diff --git a/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/JavaFormatterTest.java b/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/JavaFormatterTest.java
new file mode 100644
index 000000000..cd472fdca
--- /dev/null
+++ b/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/JavaFormatterTest.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2020 itemis AG (http://www.itemis.com) and others.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.xtext.formatting2.internal;
+
+import org.eclipse.xtext.formatting2.internal.tests.FormatterTestLanguageInjectorProvider;
+import org.eclipse.xtext.testing.InjectWith;
+import org.eclipse.xtext.testing.XtextRunner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import com.google.inject.Inject;
+
+/**
+ * @author Arne Deutsch - Initial contribution and API
+ */
+@RunWith(XtextRunner.class)
+@InjectWith(FormatterTestLanguageInjectorProvider.class)
+public class JavaFormatterTest {
+
+ @Inject
+ private JavaFormatterTestHelper helper;
+
+ @Test
+ public void javaFormatterWorks() {
+ helper.assertFormatted(" idlist a b c", "idlist a b c");
+ }
+
+}
diff --git a/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/JavaFormatterTestHelper.java b/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/JavaFormatterTestHelper.java
new file mode 100644
index 000000000..4083b5416
--- /dev/null
+++ b/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/JavaFormatterTestHelper.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2020 itemis AG (http://www.itemis.eu) and others.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.xtext.formatting2.internal;
+
+import org.eclipse.xtext.formatting2.IFormatter2;
+import org.eclipse.xtext.testing.formatter.FormatterTestHelper;
+import org.eclipse.xtext.testing.formatter.FormatterTestRequest;
+
+import com.google.inject.Inject;
+
+/**
+ * @author Arne Deutsch - Initial contribution and API
+ */
+public class JavaFormatterTestHelper extends FormatterTestHelper {
+
+ @Inject
+ private JavaFormatter formatter;
+
+ public void assertFormatted(String original, String expectation) {
+ assertFormatted(r -> r.setToBeFormatted(original).setExpectation(expectation));
+ }
+
+ @Override
+ protected IFormatter2 createFormatter(FormatterTestRequest request) {
+ return formatter;
+ }
+
+}
diff --git a/org.eclipse.xtext/META-INF/MANIFEST.MF b/org.eclipse.xtext/META-INF/MANIFEST.MF
index 1c3e412d7..e10c099fd 100644
--- a/org.eclipse.xtext/META-INF/MANIFEST.MF
+++ b/org.eclipse.xtext/META-INF/MANIFEST.MF
@@ -32,20 +32,7 @@ Export-Package: org.eclipse.xtext,
org.eclipse.xtend.core",
org.eclipse.xtext.formatting,
org.eclipse.xtext.formatting.impl,
- org.eclipse.xtext.formatting2;
- x-friends:="org.eclipse.xtext.generator,
- org.eclipse.xtext.ide,
- org.eclipse.xtext.ide.tests,
- org.eclipse.xtext.purexbase,
- org.eclipse.xtext.testing,
- org.eclipse.xtext.testlanguages,
- org.eclipse.xtext.tests,
- org.eclipse.xtext.xbase,
- org.eclipse.xtend.core,
- org.eclipse.xtend.ide,
- org.eclipse.xtext.xtext.generator,
- org.eclipse.xtext.junit4,
- org.eclipse.xtext.ui",
+ org.eclipse.xtext.formatting2,
org.eclipse.xtext.formatting2.debug;x-friends:="org.eclipse.xtext.ide,
org.eclipse.xtext.eclipse.tests,
org.eclipse.xtext.ide.tests,
@@ -54,17 +41,7 @@ Export-Package: org.eclipse.xtext,
org.eclipse.xtext.tests",
org.eclipse.xtext.formatting2.internal;x-friends:="org.eclipse.xtext.tests,
org.eclipse.xtend.core",
- org.eclipse.xtext.formatting2.regionaccess;x-friends:="org.eclipse.xtext.ide,
- org.eclipse.xtext.ide.tests,
- org.eclipse.xtext.purexbase,
- org.eclipse.xtext.xbase,
- org.eclipse.xtend.core,
- org.eclipse.xtend.ide,
- org.eclipse.xtend.core,
- org.eclipse.xtext.junit4,
- org.eclipse.xtext.testing,
- org.eclipse.xtext.tests,
- org.eclipse.xtext.ui",
+ org.eclipse.xtext.formatting2.regionaccess,
org.eclipse.xtext.formatting2.regionaccess.internal;x-friends:="org.eclipse.xtext.ide,
org.eclipse.xtend.core,
org.eclipse.xtend.ide,
diff --git a/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/AbstractJavaFormatter.java b/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/AbstractJavaFormatter.java
new file mode 100644
index 000000000..38405b3a0
--- /dev/null
+++ b/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/AbstractJavaFormatter.java
@@ -0,0 +1,156 @@
+/*******************************************************************************
+ * Copyright (c) 2020 itemis AG (http://www.itemis.eu) and others.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.xtext.formatting2;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.xtext.formatting2.regionaccess.IEObjectRegion;
+import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion;
+import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion;
+import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegionFinder;
+import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegionsFinder;
+import org.eclipse.xtext.resource.XtextResource;
+import org.eclipse.xtext.util.PolymorphicDispatcher;
+
+import com.google.common.annotations.Beta;
+
+/**
+ *
+ * This is an abstract base class for language-specific formatters, intended to be extended with a java class.
+ *
+ *
+ * It is working very much like {@link AbstractFormatter2} but does support additional methods to simplify java
+ * formatter code.
+ *
+ *
+ * the "_format" methods are called by reflection. The name is important as well as there are exactly two arguments
+ * whereas the first one is of the element type you like to format and the second one of type
+ * {@link IFormattableDocument}. The methods should be protected.
+ *
+ *
+ * Example code for a java formatter:
+ *
+ *
+ * import static org.xtext.example.mydsl.myDsl.MyDslPackage.Literals.*;
+ *
+ * public class MyDslFormatter2 extends AbstractJavaFormatter {
+ * protected void _format(Model model, IFormattableDocument doc) {
+ * for (Parent parent : model.getParents())
+ * doc.format(parent);
+ * }
+ *
+ * protected void _format(Parent parent, IFormattableDocument doc) {
+ * doc.prepend(regionFor(parent).keyword("parent"), it -> it.noSpace());
+ * doc.append(regionFor(parent).keyword("parent"), it -> it.oneSpace());
+ * doc.append(regionFor(parent).feature(PARENT__NAME), it -> it.oneSpace());
+ * doc.prepend(regionFor(parent).keyword("{"), it -> it.oneSpace());
+ * doc.append(regionFor(parent).keyword("{"), it -> it.newLine());
+ * doc.interior(regionFor(parent).keyword("{"), regionFor(parent).keyword("}"), it -> it.indent());
+ * doc.append(regionFor(parent).keyword("}"), it -> it.setNewLines(1, 1, 2));
+ * for (Child child : parent.getChildren())
+ * doc.format(child);
+ * }
+ *
+ * protected void _format(Child child, IFormattableDocument doc) {
+ * doc.append(regionFor(child).keyword("child"), it -> it.oneSpace());
+ * doc.append(regionFor(child).feature(CHILD__NAME), it -> it.newLine());
+ * }
+ * }
+ *
+ *
+ *
+ * This is suitable for the following grammar:
+ *
+ *
+ * grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.common.Terminals
+ *
+ * generate myDsl "http://www.xtext.org/example/mydsl/MyDsl"
+ *
+ * Model: parents+=Parent*;
+ * Parent: 'parent' name=ID '!' ('{' children+=Child* '}')?;
+ * Child: 'child' name=ID;
+ *
+ *
+ */
+@Beta
+public abstract class AbstractJavaFormatter extends AbstractFormatter2 {
+
+ private PolymorphicDispatcher dispatcher = createPolymorhicDispatcher();
+
+ // reflective method that calls "_format" methods found in the implementing class.
+ @Override
+ public void format(Object child, IFormattableDocument document) {
+ if (child instanceof XtextResource) {
+ _format((XtextResource) child, document);
+ return;
+ } else if (child == null) {
+ _format((Void) null, document);
+ return;
+ }
+ try {
+ dispatcher.invoke(child, document);
+ } catch (SecurityException | IllegalArgumentException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Override if you like to specify formatting methods in different way then default (using annotations or similar).
+ */
+ protected PolymorphicDispatcher createPolymorhicDispatcher() {
+ return PolymorphicDispatcher.createForSingleTarget(m -> "format".equals(m.getName())
+ && m.getParameterCount() == 2 && m.getParameterTypes()[1] == IFormattableDocument.class, this);
+ }
+
+ // implementations that forward the methods of ITextRegionExtensions to simplify formatter code.
+
+ protected ISemanticRegionsFinder allRegionsFor(EObject semanticElement) {
+ return textRegionExtensions.allRegionsFor(semanticElement);
+ }
+
+ protected Iterable allSemanticRegions(EObject semanticElement) {
+ return textRegionExtensions.allSemanticRegions(semanticElement);
+ }
+
+ protected EObject grammarElement(EObject semanticElement) {
+ return textRegionExtensions.grammarElement(semanticElement);
+ }
+
+ protected ISemanticRegionFinder immediatelyFollowing(EObject semanticElement) {
+ return textRegionExtensions.immediatelyFollowing(semanticElement);
+ }
+
+ protected ISemanticRegionFinder immediatelyPreceding(EObject semanticElement) {
+ return textRegionExtensions.immediatelyPreceding(semanticElement);
+ }
+
+ protected boolean isMultiline(EObject semanticElement) {
+ return textRegionExtensions.isMultiline(semanticElement);
+ }
+
+ protected IHiddenRegion nextHiddenRegion(EObject semanticElement) {
+ return textRegionExtensions.nextHiddenRegion(semanticElement);
+ }
+
+ protected IHiddenRegion previousHiddenRegion(EObject semanticElement) {
+ return textRegionExtensions.previousHiddenRegion(semanticElement);
+ }
+
+ protected ISemanticRegionsFinder regionFor(EObject semanticElement) {
+ return textRegionExtensions.regionFor(semanticElement);
+ }
+
+ protected IEObjectRegion regionForEObject(EObject semanticElement) {
+ return textRegionExtensions.regionForEObject(semanticElement);
+ }
+
+ protected Iterable semanticRegions(EObject semanticElement) {
+ return textRegionExtensions.semanticRegions(semanticElement);
+ }
+
+}