[#1626] Use node model to generate comments with original format.

Signed-off-by: Arne Deutsch <Arne.Deutsch@itemis.de>
This commit is contained in:
Arne Deutsch 2020-12-18 14:26:10 +01:00
parent b34bbe849a
commit 8ab3672c97
3 changed files with 160 additions and 57 deletions

View file

@ -70,19 +70,20 @@ public class GrammarAccessExtensions2Test {
" | '?:';\n"; " | '?:';\n";
String expected = String expected =
"//OpOther:" + NL + "//OpOther:" + NL +
"// '->'" + NL + "// '->'" + NL +
"// | '..<'" + NL + "// | '..<'" + NL +
"// | '>' '..'" + NL + "// | '>' '..'" + NL +
"// | '..'" + NL + "// | '..'" + NL +
"// | '=>'" + NL + "// | '=>'" + NL +
"// | '>' (=> ('>' '>') | '>') | '<' (=> ('<' '<') | '<' | '=>') | '<>'" + NL + "// | '>' (=>('>' '>') | '>')" + NL +
"// | '?:';"; "// | '<' (=>('<' '<') | '<' | '=>')" + NL +
"// | '<>'" + NL +
"// | '?:';";
firstRuleIsConvertedTo(grammar, expected); firstRuleIsConvertedTo(grammar, expected);
} }
@Test @Test
public void testGrammarFragmentToString2() throws Exception { public void testGrammarFragmentToString2() throws Exception {
String NL = System.lineSeparator();
String grammar = String grammar =
"grammar org.xtext.example.mydsl.MyDsl hidden (ML_COMMENT)\n" + "grammar org.xtext.example.mydsl.MyDsl hidden (ML_COMMENT)\n" +
"import 'http://www.eclipse.org/emf/2002/Ecore' as ecore\n" + "import 'http://www.eclipse.org/emf/2002/Ecore' as ecore\n" +
@ -91,8 +92,7 @@ public class GrammarAccessExtensions2Test {
"terminal ML_COMMENT: '/*'(!'*')->'*/';\n" + "terminal ML_COMMENT: '/*'(!'*')->'*/';\n" +
"terminal ID: '^'?('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'_'|'0'..'9')*;"; "terminal ID: '^'?('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'_'|'0'..'9')*;";
String expected = String expected =
"//terminal ML_COMMENT:" + NL "//terminal ML_COMMENT: '/*'(!'*')->'*/';";
+ "// '/*' !'*'->'*/';";
secondRuleIsConvertedTo(grammar, expected); secondRuleIsConvertedTo(grammar, expected);
} }

View file

@ -8,9 +8,7 @@
*******************************************************************************/ *******************************************************************************/
package org.eclipse.xtext.xtext.generator.grammarAccess package org.eclipse.xtext.xtext.generator.grammarAccess
import com.google.common.collect.Maps
import com.google.inject.Binder import com.google.inject.Binder
import com.google.inject.Guice
import com.google.inject.Inject import com.google.inject.Inject
import java.util.ArrayList import java.util.ArrayList
import java.util.List import java.util.List
@ -29,16 +27,17 @@ import org.eclipse.xtext.Grammar
import org.eclipse.xtext.GrammarUtil import org.eclipse.xtext.GrammarUtil
import org.eclipse.xtext.Group import org.eclipse.xtext.Group
import org.eclipse.xtext.Keyword import org.eclipse.xtext.Keyword
import org.eclipse.xtext.Parameter
import org.eclipse.xtext.ParserRule import org.eclipse.xtext.ParserRule
import org.eclipse.xtext.RuleCall import org.eclipse.xtext.RuleCall
import org.eclipse.xtext.TypeRef import org.eclipse.xtext.TypeRef
import org.eclipse.xtext.UnorderedGroup import org.eclipse.xtext.UnorderedGroup
import org.eclipse.xtext.XtextRuntimeModule import org.eclipse.xtext.XtextRuntimeModule
import org.eclipse.xtext.formatting.ILineSeparatorInformation import org.eclipse.xtext.formatting.ILineSeparatorInformation
import org.eclipse.xtext.nodemodel.util.NodeModelUtils
import org.eclipse.xtext.resource.SaveOptions import org.eclipse.xtext.resource.SaveOptions
import org.eclipse.xtext.serializer.ISerializer import org.eclipse.xtext.serializer.ISerializer
import org.eclipse.xtext.xtext.RuleNames import org.eclipse.xtext.xtext.RuleNames
import org.eclipse.xtext.xtext.generator.CodeConfig
import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming
import org.eclipse.xtext.xtext.generator.model.TypeReference import org.eclipse.xtext.xtext.generator.model.TypeReference
import org.eclipse.xtext.xtext.generator.parser.antlr.AntlrGrammarGenUtil import org.eclipse.xtext.xtext.generator.parser.antlr.AntlrGrammarGenUtil
@ -48,7 +47,8 @@ import static extension java.lang.Character.*
import static extension org.eclipse.xtext.EcoreUtil2.* import static extension org.eclipse.xtext.EcoreUtil2.*
import static extension org.eclipse.xtext.GrammarUtil.* import static extension org.eclipse.xtext.GrammarUtil.*
import static extension org.eclipse.xtext.xtext.generator.parser.antlr.AntlrGrammarGenUtil.* import static extension org.eclipse.xtext.xtext.generator.parser.antlr.AntlrGrammarGenUtil.*
import org.eclipse.xtext.Parameter import com.google.common.base.Strings
import org.eclipse.xtext.xtext.generator.CodeConfig
/** /**
* This API can be used by other templates to generate code * This API can be used by other templates to generate code
@ -66,11 +66,9 @@ class GrammarAccessExtensions {
"\t" -> "tab", "\t" -> "tab",
"\\" -> "backslash" "\\" -> "backslash"
}; };
val Map<String, ISerializer> xtextSerializerByLineDelimiter = Maps.newHashMapWithExpectedSize(2)
@Inject CodeConfig codeConfig
@Inject extension XtextGeneratorNaming @Inject extension XtextGeneratorNaming
@Inject CodeConfig codeConfig
/** /**
* Returns a reference to the GrammarAccess implementation for a grammar. * Returns a reference to the GrammarAccess implementation for a grammar.
@ -396,8 +394,59 @@ class GrammarAccessExtensions {
} }
} }
def String grammarFragmentToString(EObject ele, String prefix) { def String grammarFragmentToString(EObject object, String prefix) {
return serializer.grammarFragmentToString(ele, prefix) val node = NodeModelUtils.findActualNodeFor(object)
if (node === null) {
if (object instanceof RuleCall)
if (object?.rule?.name !== null)
return process(object.rule.name, prefix)
return ""
} else {
return node.text.process(prefix)
}
}
private def process(String input, String prefix) {
// remove leading and trailing blank lines
var lines = input.split('\\s*(\\r?\\n)')
var first = 0
while (isBlank(lines.get(first)))
first++
var last = lines.length - 1
while (isBlank(lines.get(last)))
last--
lines = lines.subList(first, last + 1)
// just one line, trim it and be done
if (lines.size() == 1)
return prefix + lines.get(0).trim();
// remove common whitespace (e.g. leading blanks) and add prefix to each line
val commonWhitespace = commonLeadingWhitespace(lines)
for (var n=0; n<lines.length; n++)
lines.set(n, prefix + lines.get(n).replaceAll("\t", " ").substring(commonWhitespace.length))
// generate result by joining lines
return lines.join(codeConfig.lineDelimiter)
}
private def isBlank(String line) {
return line.trim.empty
}
private def String commonLeadingWhitespace(List<String> lines) {
if(lines.size() < 2) return "";
// determine common prefix while ignoring blank lines
var current = Strings.repeat(" ", lines.get(0).replaceAll("\t", " ").length())
for (var i = 0; i < lines.length; i++) {
val next = lines.get(i).replaceAll("\t", " ");
if (!isBlank(next))
current = Strings.commonPrefix(current, next);
}
// replace blank lines with prefix to handle empty lines gracefully
for (var i = 0; i < lines.length; i++) {
val next = lines.get(i).replaceAll("\t", " ");
if (isBlank(next))
lines.set(i, current)
}
return current;
} }
/** /**
@ -546,18 +595,6 @@ class GrammarAccessExtensions {
} }
} }
private def ISerializer getSerializer() {
val delimiter = codeConfig.lineDelimiter
var result = xtextSerializerByLineDelimiter.get(delimiter)
if (result !== null) {
return result
}
val injector = Guice.createInjector(new LineSeparatorModule[delimiter])
result = injector.getInstance(ISerializer)
xtextSerializerByLineDelimiter.put(delimiter, result)
return result;
}
@FinalFieldsConstructor @FinalFieldsConstructor
protected static class LineSeparatorModule extends XtextRuntimeModule { protected static class LineSeparatorModule extends XtextRuntimeModule {

View file

@ -9,12 +9,10 @@
package org.eclipse.xtext.xtext.generator.grammarAccess; package org.eclipse.xtext.xtext.generator.grammarAccess;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.inject.Binder; import com.google.inject.Binder;
import com.google.inject.Guice;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Injector;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -45,10 +43,13 @@ import org.eclipse.xtext.TypeRef;
import org.eclipse.xtext.UnorderedGroup; import org.eclipse.xtext.UnorderedGroup;
import org.eclipse.xtext.XtextRuntimeModule; import org.eclipse.xtext.XtextRuntimeModule;
import org.eclipse.xtext.formatting.ILineSeparatorInformation; import org.eclipse.xtext.formatting.ILineSeparatorInformation;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.resource.SaveOptions; import org.eclipse.xtext.resource.SaveOptions;
import org.eclipse.xtext.serializer.ISerializer; import org.eclipse.xtext.serializer.ISerializer;
import org.eclipse.xtext.service.CompoundModule; import org.eclipse.xtext.service.CompoundModule;
import org.eclipse.xtext.xbase.lib.CollectionLiterals; import org.eclipse.xtext.xbase.lib.CollectionLiterals;
import org.eclipse.xtext.xbase.lib.Conversions;
import org.eclipse.xtext.xbase.lib.Exceptions; import org.eclipse.xtext.xbase.lib.Exceptions;
import org.eclipse.xtext.xbase.lib.Extension; import org.eclipse.xtext.xbase.lib.Extension;
import org.eclipse.xtext.xbase.lib.Functions.Function1; import org.eclipse.xtext.xbase.lib.Functions.Function1;
@ -93,15 +94,13 @@ public class GrammarAccessExtensions {
private static Map<String, String> SPECIAL_CHARS = Collections.<String, String>unmodifiableMap(CollectionLiterals.<String, String>newHashMap(Pair.<String, String>of("\b", "backspace"), Pair.<String, String>of("\f", "formFeed"), Pair.<String, String>of("\n", "lineFeed"), Pair.<String, String>of("\r", "carriageReturn"), Pair.<String, String>of("\t", "tab"), Pair.<String, String>of("\\", "backslash"))); private static Map<String, String> SPECIAL_CHARS = Collections.<String, String>unmodifiableMap(CollectionLiterals.<String, String>newHashMap(Pair.<String, String>of("\b", "backspace"), Pair.<String, String>of("\f", "formFeed"), Pair.<String, String>of("\n", "lineFeed"), Pair.<String, String>of("\r", "carriageReturn"), Pair.<String, String>of("\t", "tab"), Pair.<String, String>of("\\", "backslash")));
private final Map<String, ISerializer> xtextSerializerByLineDelimiter = Maps.<String, ISerializer>newHashMapWithExpectedSize(2);
@Inject
private CodeConfig codeConfig;
@Inject @Inject
@Extension @Extension
private XtextGeneratorNaming _xtextGeneratorNaming; private XtextGeneratorNaming _xtextGeneratorNaming;
@Inject
private CodeConfig codeConfig;
/** /**
* Returns a reference to the GrammarAccess implementation for a grammar. * Returns a reference to the GrammarAccess implementation for a grammar.
*/ */
@ -600,8 +599,91 @@ public class GrammarAccessExtensions {
return _switchResult; return _switchResult;
} }
public String grammarFragmentToString(final EObject ele, final String prefix) { public String grammarFragmentToString(final EObject object, final String prefix) {
return GrammarAccessExtensions.grammarFragmentToString(this.getSerializer(), ele, prefix); final ICompositeNode node = NodeModelUtils.findActualNodeFor(object);
if ((node == null)) {
if ((object instanceof RuleCall)) {
AbstractRule _rule = null;
if (((RuleCall)object)!=null) {
_rule=((RuleCall)object).getRule();
}
String _name = null;
if (_rule!=null) {
_name=_rule.getName();
}
boolean _tripleNotEquals = (_name != null);
if (_tripleNotEquals) {
return this.process(((RuleCall)object).getRule().getName(), prefix);
}
}
return "";
} else {
return this.process(node.getText(), prefix);
}
}
private String process(final String input, final String prefix) {
String[] lines = input.split("\\s*(\\r?\\n)");
int first = 0;
while (this.isBlank(lines[first])) {
first++;
}
int _length = lines.length;
int last = (_length - 1);
while (this.isBlank(lines[last])) {
last--;
}
final String[] _converted_lines = (String[])lines;
lines = ((String[])Conversions.unwrapArray(((List<String>)Conversions.doWrapArray(_converted_lines)).subList(first, (last + 1)), String.class));
final String[] _converted_lines_1 = (String[])lines;
int _size = ((List<String>)Conversions.doWrapArray(_converted_lines_1)).size();
boolean _equals = (_size == 1);
if (_equals) {
String _trim = (lines[0]).trim();
return (prefix + _trim);
}
final String[] _converted_lines_2 = (String[])lines;
final String commonWhitespace = this.commonLeadingWhitespace(((List<String>)Conversions.doWrapArray(_converted_lines_2)));
for (int n = 0; (n < lines.length); n++) {
String _substring = (lines[n]).replaceAll("\t", " ").substring(commonWhitespace.length());
String _plus = (prefix + _substring);
lines[n] = _plus;
}
final String[] _converted_lines_3 = (String[])lines;
return IterableExtensions.join(((Iterable<?>)Conversions.doWrapArray(_converted_lines_3)), this.codeConfig.getLineDelimiter());
}
private boolean isBlank(final String line) {
return line.trim().isEmpty();
}
private String commonLeadingWhitespace(final List<String> lines) {
int _size = lines.size();
boolean _lessThan = (_size < 2);
if (_lessThan) {
return "";
}
String current = Strings.repeat(" ", lines.get(0).replaceAll("\t", " ").length());
for (int i = 0; (i < ((Object[])Conversions.unwrapArray(lines, Object.class)).length); i++) {
{
final String next = lines.get(i).replaceAll("\t", " ");
boolean _isBlank = this.isBlank(next);
boolean _not = (!_isBlank);
if (_not) {
current = Strings.commonPrefix(current, next);
}
}
}
for (int i = 0; (i < ((Object[])Conversions.unwrapArray(lines, Object.class)).length); i++) {
{
final String next = lines.get(i).replaceAll("\t", " ");
boolean _isBlank = this.isBlank(next);
if (_isBlank) {
lines.set(i, current);
}
}
}
return current;
} }
/** /**
@ -862,22 +944,6 @@ public class GrammarAccessExtensions {
return _switchResult; return _switchResult;
} }
private ISerializer getSerializer() {
final String delimiter = this.codeConfig.getLineDelimiter();
ISerializer result = this.xtextSerializerByLineDelimiter.get(delimiter);
if ((result != null)) {
return result;
}
final ILineSeparatorInformation _function = () -> {
return delimiter;
};
GrammarAccessExtensions.LineSeparatorModule _lineSeparatorModule = new GrammarAccessExtensions.LineSeparatorModule(_function);
final Injector injector = Guice.createInjector(_lineSeparatorModule);
result = injector.<ISerializer>getInstance(ISerializer.class);
this.xtextSerializerByLineDelimiter.put(delimiter, result);
return result;
}
public String grammarElementIdentifier(final EObject it) { public String grammarElementIdentifier(final EObject it) {
if (it instanceof AbstractElement) { if (it instanceof AbstractElement) {
return _grammarElementIdentifier((AbstractElement)it); return _grammarElementIdentifier((AbstractElement)it);