diff --git a/org.eclipse.xtext.tests/src/org/eclipse/xtext/generator/trace/node/GeneratorNodeTest.xtend b/org.eclipse.xtext.tests/src/org/eclipse/xtext/generator/trace/node/GeneratorNodeTest.xtend new file mode 100644 index 000000000..98e6b345b --- /dev/null +++ b/org.eclipse.xtext.tests/src/org/eclipse/xtext/generator/trace/node/GeneratorNodeTest.xtend @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2017 TypeFox (https://typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.generator.trace.node + +import org.eclipse.xtend2.lib.StringConcatenationClient +import org.eclipse.xtext.generator.trace.LocationData +import org.eclipse.xtext.generator.trace.SourceRelativeURI +import org.junit.Assert +import org.junit.Test + +/** + * @author Sven Efftinge - Initial contribution and API + */ +class GeneratorNodeTest { + + extension GeneratorNodeExtensions exts = new GeneratorNodeExtensions() + + @Test def void testBasicCreationAndProcessing() { + val root = loc(0) + var node = root.trace + .append('notindented').appendNewLine + node.indent.trace(loc(1)) + .append("indented1").appendNewLine + .append("indented2") + node.appendNewLine.append("dedented") + val processor = new GeneratorNodeProcessor() + val result = processor.process(node) + Assert.assertEquals(''' + notindented + indented1 + indented2 + dedented'''.toString, result.toString) + Assert.assertEquals(''' + CompletableTraceRegion [myOffset=0, myLength=44] associations={ + LocationData [TextRegionWithLineInformation [0:100][lineNumber=0, endLineNumber=0]][path=foo/mymodel.dsl] + } nestedRegions={ + CompletableTraceRegion [myOffset=14, myLength=21] associations={ + LocationData [TextRegionWithLineInformation [1:99][lineNumber=0, endLineNumber=0]][path=foo/mymodel.dsl] + } + }'''.toString, result.traceRegion.toString) + } + + + @Test def void testTemplateProcessing() { + val root = loc(0) + var node = root.trace + .appendTemplate(''' + «someCodeGen(2)» + ''') + + val processor = new GeneratorNodeProcessor() + val result = processor.process(node) + Assert.assertEquals(someCodeGen_noTrace(2).toString, result.toString) + Assert.assertEquals(''' + CompletableTraceRegion [myOffset=0, myLength=80] associations={ + LocationData [TextRegionWithLineInformation [0:100][lineNumber=0, endLineNumber=0]][path=foo/mymodel.dsl] + } nestedRegions={ + CompletableTraceRegion [myOffset=7, myLength=5] associations={ + LocationData [TextRegionWithLineInformation [10:90][lineNumber=0, endLineNumber=0]][path=foo/mymodel.dsl] + } + CompletableTraceRegion [myOffset=28, myLength=5] associations={ + LocationData [TextRegionWithLineInformation [10:90][lineNumber=0, endLineNumber=0]][path=foo/mymodel.dsl] + } + CompletableTraceRegion [myOffset=47, myLength=5] associations={ + LocationData [TextRegionWithLineInformation [11:89][lineNumber=0, endLineNumber=0]][path=foo/mymodel.dsl] + } + CompletableTraceRegion [myOffset=68, myLength=5] associations={ + LocationData [TextRegionWithLineInformation [10:90][lineNumber=0, endLineNumber=0]][path=foo/mymodel.dsl] + } + }'''.toString, result.traceRegion.toString) + } + + def StringConcatenationClient someCodeGen(int n) ''' + «FOR i : 0.. 1»s + ee «FOR i : 1..4» + «''» + «other» «i» + «multiLineString» «multiLineString» + «ENDFOR» + «ENDIF» + ''') + } + + private def other() ''' + foo «"dfdf" + 23» bar + ''' + + private def String multiLineString() ''' + test + bar + «other()» + ''' + + def void assertEquals(StringConcatenationClient c) { + val ext = new GeneratorNodeExtensions() + val processor = new GeneratorNodeProcessor() + + val root = new CompositeGeneratorNode() + ext.appendTemplate(root, c); + val result = processor.process(root) + + val expected = new StringConcatenation() + expected.append(c) + Assert.assertEquals(expected.toString, result.toString) + } +} \ No newline at end of file diff --git a/org.eclipse.xtext.tests/src/org/eclipse/xtext/generator/trace/node/TracingSugarTest.xtend b/org.eclipse.xtext.tests/src/org/eclipse/xtext/generator/trace/node/TracingSugarTest.xtend new file mode 100644 index 000000000..2fff38db9 --- /dev/null +++ b/org.eclipse.xtext.tests/src/org/eclipse/xtext/generator/trace/node/TracingSugarTest.xtend @@ -0,0 +1,135 @@ +/******************************************************************************* + * Copyright (c) 2017 TypeFox (https://typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.generator.trace.node + +import com.google.inject.Inject +import org.eclipse.xtext.generator.InMemoryFileSystemAccess +import org.eclipse.xtext.linking.lazy.lazyLinking.LazyLinkingFactory +import org.eclipse.xtext.linking.lazy.lazyLinking.Model +import org.eclipse.xtext.linking.lazy.lazyLinking.Property +import org.eclipse.xtext.linking.lazy.lazyLinking.Type +import org.eclipse.xtext.linking.lazy.tests.LazyLinkingTestLanguageInjectorProvider +import org.eclipse.xtext.testing.InjectWith +import org.eclipse.xtext.testing.XtextRunner +import org.eclipse.xtext.testing.util.ParseHelper +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.Assert +import org.eclipse.xtext.generator.trace.ITraceRegionProvider + +/** + * @author Sven Efftinge - Initial contribution and API + */ +@RunWith(XtextRunner) +@InjectWith(LazyLinkingTestLanguageInjectorProvider) +class TracingSugarTest { + + @TracedAccessors(LazyLinkingFactory) + static class MyExtensions { + + /** + * manual implementation for unsupported multi cross reference + */ + def IGeneratorNode _type(Property it, (Type)=>String provider) { + val location = location(eClass.getEStructuralFeature("type"), 0) + val result = location.trace + result.append(provider.apply(type.head)) + return result + } + } + + @Inject extension MyExtensions + @Inject ParseHelper parseHelper + + CharSequence generated + + InMemoryFileSystemAccess fsa = new InMemoryFileSystemAccess() { + override generateFile(String fileName, CharSequence contents) { + generated = contents + super.generateFile(fileName, contents) + } + }; + + @Test def void testCodeGeneration() { + val root = parseHelper.parse(''' + type String {} + type Foo { + String name; + } + ''') + fsa.generateTracedFile('foo/bar.txt', root, ''' + «FOR t : root.types» + «t._generateType» + «ENDFOR» + ''') + + // check the generated string is as expected + Assert.assertEquals(''' + «FOR t : root.types» + «t.generateType» + «ENDFOR» + '''.toString, generated.toString) + + val trace = (generated as ITraceRegionProvider).traceRegion + Assert.assertEquals(''' + CompletableTraceRegion [myOffset=0, myLength=55] associations={ + LocationData [TextRegionWithLineInformation [0:41][lineNumber=0, endLineNumber=3]][path=__synthetic0.lazylinkingtestlanguage] + } nestedRegions={ + CompletableTraceRegion [myOffset=0, myLength=17] associations={ + LocationData [TextRegionWithLineInformation [0:14][lineNumber=0, endLineNumber=0]][path=__synthetic0.lazylinkingtestlanguage] + } nestedRegions={ + CompletableTraceRegion [myOffset=6, myLength=6] associations={ + LocationData [TextRegionWithLineInformation [5:6][lineNumber=0, endLineNumber=0]][path=__synthetic0.lazylinkingtestlanguage] + } + } + CompletableTraceRegion [myOffset=17, myLength=38] associations={ + LocationData [TextRegionWithLineInformation [15:26][lineNumber=1, endLineNumber=3]][path=__synthetic0.lazylinkingtestlanguage] + } nestedRegions={ + CompletableTraceRegion [myOffset=23, myLength=3] associations={ + LocationData [TextRegionWithLineInformation [20:3][lineNumber=1, endLineNumber=1]][path=__synthetic0.lazylinkingtestlanguage] + } + CompletableTraceRegion [myOffset=30, myLength=24] associations={ + LocationData [TextRegionWithLineInformation [27:12][lineNumber=2, endLineNumber=2]][path=__synthetic0.lazylinkingtestlanguage] + } nestedRegions={ + CompletableTraceRegion [myOffset=39, myLength=4] associations={ + LocationData [TextRegionWithLineInformation [34:4][lineNumber=2, endLineNumber=2]][path=__synthetic0.lazylinkingtestlanguage] + } + CompletableTraceRegion [myOffset=46, myLength=6] associations={ + LocationData [TextRegionWithLineInformation [27:6][lineNumber=2, endLineNumber=2]][path=__synthetic0.lazylinkingtestlanguage] + } + } + } + }'''.toString, trace.toString) + } + + @Traced def _generateType(Type it) ''' + Class «_name» { + «FOR p : properties» + «p._generateProperty» + «ENDFOR» + } + ''' + + @Traced def _generateProperty(Property it) ''' + Property «_name» : «_type[name]» + ''' + + def generateType(Type it) ''' + Class «name» { + «FOR p : properties» + «p.generateProperty» + «ENDFOR» + } + ''' + + def generateProperty(Property it) ''' + Property «name» : «type.head.name» + ''' + + +} \ No newline at end of file diff --git a/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.eclipsePlugin/org.xtext.example.eclipsePlugin/.launch/Launch Runtime Eclipse.launch b/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.eclipsePlugin/org.xtext.example.eclipsePlugin/.launch/Launch Runtime Eclipse.launch index c6c201998..b4c7d5a9c 100644 --- a/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.eclipsePlugin/org.xtext.example.eclipsePlugin/.launch/Launch Runtime Eclipse.launch +++ b/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.eclipsePlugin/org.xtext.example.eclipsePlugin/.launch/Launch Runtime Eclipse.launch @@ -18,7 +18,7 @@ - + diff --git a/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.eclipsePluginP2/org.xtext.example.eclipsePluginP2/.launch/Launch Runtime Eclipse.launch b/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.eclipsePluginP2/org.xtext.example.eclipsePluginP2/.launch/Launch Runtime Eclipse.launch index 9d65e4864..b7e836bf3 100644 --- a/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.eclipsePluginP2/org.xtext.example.eclipsePluginP2/.launch/Launch Runtime Eclipse.launch +++ b/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.eclipsePluginP2/org.xtext.example.eclipsePluginP2/.launch/Launch Runtime Eclipse.launch @@ -18,7 +18,7 @@ - + diff --git a/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.full/org.xtext.example.full.parent/gradle/source-layout.gradle b/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.full/org.xtext.example.full.parent/gradle/source-layout.gradle index 89d7423a7..df1f2ee83 100644 --- a/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.full/org.xtext.example.full.parent/gradle/source-layout.gradle +++ b/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.full/org.xtext.example.full.parent/gradle/source-layout.gradle @@ -25,7 +25,7 @@ if (name.endsWith(".tests")) { } sourceSets.all { - resources.exclude '**/*.g', '**/*.xtext', '**/*.mwe2', '**/*.xtend', '**/*._trace' + resources.exclude '**/*.g', '**/*.mwe2', '**/*.xtend', '**/*._trace' } jar { diff --git a/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.full/org.xtext.example.full.parent/org.xtext.example.full/.launch/Launch Runtime Eclipse.launch b/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.full/org.xtext.example.full.parent/org.xtext.example.full/.launch/Launch Runtime Eclipse.launch index 7b4c9df46..cdff3df6c 100644 --- a/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.full/org.xtext.example.full.parent/org.xtext.example.full/.launch/Launch Runtime Eclipse.launch +++ b/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.full/org.xtext.example.full.parent/org.xtext.example.full/.launch/Launch Runtime Eclipse.launch @@ -18,7 +18,7 @@ - + diff --git a/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.gradle/org.xtext.example.gradle.parent/gradle/source-layout.gradle b/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.gradle/org.xtext.example.gradle.parent/gradle/source-layout.gradle index 2296c8c3d..fe383a5ad 100644 --- a/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.gradle/org.xtext.example.gradle.parent/gradle/source-layout.gradle +++ b/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.gradle/org.xtext.example.gradle.parent/gradle/source-layout.gradle @@ -15,6 +15,9 @@ jar { from('model') { into('model') } + from(sourceSets.main.allSource) { + include '**/*.xtext' + } manifest { attributes 'Bundle-SymbolicName': project.name } diff --git a/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.mavenTycho/org.xtext.example.mavenTycho.parent/org.xtext.example.mavenTycho/.launch/Launch Runtime Eclipse.launch b/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.mavenTycho/org.xtext.example.mavenTycho.parent/org.xtext.example.mavenTycho/.launch/Launch Runtime Eclipse.launch index b0685b532..d655ad3d8 100644 --- a/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.mavenTycho/org.xtext.example.mavenTycho.parent/org.xtext.example.mavenTycho/.launch/Launch Runtime Eclipse.launch +++ b/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.mavenTycho/org.xtext.example.mavenTycho.parent/org.xtext.example.mavenTycho/.launch/Launch Runtime Eclipse.launch @@ -18,7 +18,7 @@ - + diff --git a/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.mavenTychoP2/org.xtext.example.mavenTychoP2.parent/org.xtext.example.mavenTychoP2/.launch/Launch Runtime Eclipse.launch b/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.mavenTychoP2/org.xtext.example.mavenTychoP2.parent/org.xtext.example.mavenTychoP2/.launch/Launch Runtime Eclipse.launch index bf1808506..3b9d91a7c 100644 --- a/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.mavenTychoP2/org.xtext.example.mavenTychoP2.parent/org.xtext.example.mavenTychoP2/.launch/Launch Runtime Eclipse.launch +++ b/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.mavenTychoP2/org.xtext.example.mavenTychoP2.parent/org.xtext.example.mavenTychoP2/.launch/Launch Runtime Eclipse.launch @@ -18,7 +18,7 @@ - + diff --git a/org.eclipse.xtext.tests/xtend-gen/org/eclipse/xtext/generator/trace/node/GeneratorNodeTest.java b/org.eclipse.xtext.tests/xtend-gen/org/eclipse/xtext/generator/trace/node/GeneratorNodeTest.java new file mode 100644 index 000000000..4df1c2ca5 --- /dev/null +++ b/org.eclipse.xtext.tests/xtend-gen/org/eclipse/xtext/generator/trace/node/GeneratorNodeTest.java @@ -0,0 +1,179 @@ +/** + * Copyright (c) 2017 TypeFox (https://typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.xtext.generator.trace.node; + +import org.eclipse.xtend2.lib.StringConcatenation; +import org.eclipse.xtend2.lib.StringConcatenationClient; +import org.eclipse.xtext.generator.trace.LocationData; +import org.eclipse.xtext.generator.trace.SourceRelativeURI; +import org.eclipse.xtext.generator.trace.node.CompositeGeneratorNode; +import org.eclipse.xtext.generator.trace.node.GeneratorNodeExtensions; +import org.eclipse.xtext.generator.trace.node.GeneratorNodeProcessor; +import org.eclipse.xtext.xbase.lib.ExclusiveRange; +import org.eclipse.xtext.xbase.lib.Extension; +import org.junit.Assert; +import org.junit.Test; + +/** + * @author Sven Efftinge - Initial contribution and API + */ +@SuppressWarnings("all") +public class GeneratorNodeTest { + @Extension + private GeneratorNodeExtensions exts = new GeneratorNodeExtensions(); + + @Test + public void testBasicCreationAndProcessing() { + final LocationData root = this.loc(0); + CompositeGeneratorNode node = this.exts.appendNewLine(this.exts.append(this.exts.trace(root), "notindented")); + this.exts.append(this.exts.appendNewLine(this.exts.append(this.exts.trace(this.exts.indent(node), this.loc(1)), "indented1")), "indented2"); + this.exts.append(this.exts.appendNewLine(node), "dedented"); + final GeneratorNodeProcessor processor = new GeneratorNodeProcessor(); + final GeneratorNodeProcessor.Result result = processor.process(node); + StringConcatenation _builder = new StringConcatenation(); + _builder.append("notindented"); + _builder.newLine(); + _builder.append(" "); + _builder.append("indented1"); + _builder.newLine(); + _builder.append(" "); + _builder.append("indented2"); + _builder.newLine(); + _builder.append("dedented"); + Assert.assertEquals(_builder.toString(), result.toString()); + StringConcatenation _builder_1 = new StringConcatenation(); + _builder_1.append("CompletableTraceRegion [myOffset=0, myLength=44] associations={"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("LocationData [TextRegionWithLineInformation [0:100][lineNumber=0, endLineNumber=0]][path=foo/mymodel.dsl]"); + _builder_1.newLine(); + _builder_1.append("} nestedRegions={"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("CompletableTraceRegion [myOffset=14, myLength=21] associations={"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("LocationData [TextRegionWithLineInformation [1:99][lineNumber=0, endLineNumber=0]][path=foo/mymodel.dsl]"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("}"); + _builder_1.newLine(); + _builder_1.append("}"); + Assert.assertEquals(_builder_1.toString(), result.getTraceRegion().toString()); + } + + @Test + public void testTemplateProcessing() { + final LocationData root = this.loc(0); + CompositeGeneratorNode _trace = this.exts.trace(root); + StringConcatenationClient _client = new StringConcatenationClient() { + @Override + protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) { + StringConcatenationClient _someCodeGen = GeneratorNodeTest.this.someCodeGen(2); + _builder.append(_someCodeGen); + _builder.newLineIfNotEmpty(); + } + }; + CompositeGeneratorNode node = this.exts.appendTemplate(_trace, _client); + final GeneratorNodeProcessor processor = new GeneratorNodeProcessor(); + final GeneratorNodeProcessor.Result result = processor.process(node); + Assert.assertEquals(this.someCodeGen_noTrace(2).toString(), result.toString()); + StringConcatenation _builder = new StringConcatenation(); + _builder.append("CompletableTraceRegion [myOffset=0, myLength=80] associations={"); + _builder.newLine(); + _builder.append(" "); + _builder.append("LocationData [TextRegionWithLineInformation [0:100][lineNumber=0, endLineNumber=0]][path=foo/mymodel.dsl]"); + _builder.newLine(); + _builder.append("} nestedRegions={"); + _builder.newLine(); + _builder.append(" "); + _builder.append("CompletableTraceRegion [myOffset=7, myLength=5] associations={"); + _builder.newLine(); + _builder.append(" "); + _builder.append("LocationData [TextRegionWithLineInformation [10:90][lineNumber=0, endLineNumber=0]][path=foo/mymodel.dsl]"); + _builder.newLine(); + _builder.append(" "); + _builder.append("}"); + _builder.newLine(); + _builder.append(" "); + _builder.append("CompletableTraceRegion [myOffset=28, myLength=5] associations={"); + _builder.newLine(); + _builder.append(" "); + _builder.append("LocationData [TextRegionWithLineInformation [10:90][lineNumber=0, endLineNumber=0]][path=foo/mymodel.dsl]"); + _builder.newLine(); + _builder.append(" "); + _builder.append("}"); + _builder.newLine(); + _builder.append(" "); + _builder.append("CompletableTraceRegion [myOffset=47, myLength=5] associations={"); + _builder.newLine(); + _builder.append(" "); + _builder.append("LocationData [TextRegionWithLineInformation [11:89][lineNumber=0, endLineNumber=0]][path=foo/mymodel.dsl]"); + _builder.newLine(); + _builder.append(" "); + _builder.append("}"); + _builder.newLine(); + _builder.append(" "); + _builder.append("CompletableTraceRegion [myOffset=68, myLength=5] associations={"); + _builder.newLine(); + _builder.append(" "); + _builder.append("LocationData [TextRegionWithLineInformation [10:90][lineNumber=0, endLineNumber=0]][path=foo/mymodel.dsl]"); + _builder.newLine(); + _builder.append(" "); + _builder.append("}"); + _builder.newLine(); + _builder.append("}"); + Assert.assertEquals(_builder.toString(), result.getTraceRegion().toString()); + } + + public StringConcatenationClient someCodeGen(final int n) { + StringConcatenationClient _client = new StringConcatenationClient() { + @Override + protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) { + { + ExclusiveRange _doubleDotLessThan = new ExclusiveRange(0, n, true); + for(final Integer i : _doubleDotLessThan) { + _builder.append("before "); + CompositeGeneratorNode _append = GeneratorNodeTest.this.exts.append(GeneratorNodeTest.this.exts.trace(GeneratorNodeTest.this.loc((10 + (i).intValue()))), "Hello"); + _builder.append(_append); + _builder.append(" after"); + _builder.newLineIfNotEmpty(); + _builder.append(" "); + StringConcatenationClient _someCodeGen = GeneratorNodeTest.this.someCodeGen((n - 1)); + _builder.append(_someCodeGen, " "); + _builder.newLineIfNotEmpty(); + } + } + } + }; + return _client; + } + + public String someCodeGen_noTrace(final int n) { + StringConcatenation _builder = new StringConcatenation(); + { + ExclusiveRange _doubleDotLessThan = new ExclusiveRange(0, n, true); + for(final Integer i : _doubleDotLessThan) { + _builder.append("before "); + _builder.append("Hello"); + _builder.append(" after"); + _builder.newLineIfNotEmpty(); + _builder.append(" "); + String _someCodeGen_noTrace = this.someCodeGen_noTrace((n - 1)); + _builder.append(_someCodeGen_noTrace, " "); + _builder.newLineIfNotEmpty(); + } + } + return _builder.toString(); + } + + public LocationData loc(final int idx) { + SourceRelativeURI _sourceRelativeURI = new SourceRelativeURI("foo/mymodel.dsl"); + return new LocationData(idx, (100 - idx), 0, 0, _sourceRelativeURI); + } +} diff --git a/org.eclipse.xtext.tests/xtend-gen/org/eclipse/xtext/generator/trace/node/TemplateNodeTest.java b/org.eclipse.xtext.tests/xtend-gen/org/eclipse/xtext/generator/trace/node/TemplateNodeTest.java new file mode 100644 index 000000000..5dad89cb3 --- /dev/null +++ b/org.eclipse.xtext.tests/xtend-gen/org/eclipse/xtext/generator/trace/node/TemplateNodeTest.java @@ -0,0 +1,123 @@ +/** + * Copyright (c) 2017 TypeFox (https://typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.xtext.generator.trace.node; + +import org.eclipse.xtend2.lib.StringConcatenation; +import org.eclipse.xtend2.lib.StringConcatenationClient; +import org.eclipse.xtext.generator.trace.node.CompositeGeneratorNode; +import org.eclipse.xtext.generator.trace.node.GeneratorNodeExtensions; +import org.eclipse.xtext.generator.trace.node.GeneratorNodeProcessor; +import org.eclipse.xtext.xbase.lib.IntegerRange; +import org.junit.Assert; +import org.junit.Test; + +/** + * @author Sven Efftinge - Initial contribution and API + */ +@SuppressWarnings("all") +public class TemplateNodeTest { + @Test + public void testSimpleString() { + StringConcatenationClient _client = new StringConcatenationClient() { + @Override + protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) { + _builder.append("fooo bar"); + _builder.newLine(); + } + }; + this.assertEquals(_client); + } + + @Test + public void testSimpleTemplate_01() { + StringConcatenationClient _client = new StringConcatenationClient() { + @Override + protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) { + String _multiLineString = TemplateNodeTest.this.multiLineString(); + _builder.append(_multiLineString); + _builder.append(" "); + String _multiLineString_1 = TemplateNodeTest.this.multiLineString(); + _builder.append(_multiLineString_1); + _builder.newLineIfNotEmpty(); + } + }; + this.assertEquals(_client); + } + + @Test + public void testWeirdTemplateString() { + StringConcatenationClient _client = new StringConcatenationClient() { + @Override + protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) { + _builder.append("fooo bar"); + _builder.newLine(); + _builder.append("d "); + { + if ((2 > 1)) { + _builder.append("s"); + _builder.newLineIfNotEmpty(); + _builder.append("\t "); + _builder.append("ee "); + { + IntegerRange _upTo = new IntegerRange(1, 4); + for(final Integer i : _upTo) { + _builder.newLineIfNotEmpty(); + _builder.newLineIfNotEmpty(); + CharSequence _other = TemplateNodeTest.this.other(); + _builder.append(_other); + _builder.append(" "); + _builder.append(i); + _builder.newLineIfNotEmpty(); + String _multiLineString = TemplateNodeTest.this.multiLineString(); + _builder.append(_multiLineString); + _builder.append(" "); + String _multiLineString_1 = TemplateNodeTest.this.multiLineString(); + _builder.append(_multiLineString_1); + _builder.newLineIfNotEmpty(); + } + } + } + } + } + }; + this.assertEquals(_client); + } + + private CharSequence other() { + StringConcatenation _builder = new StringConcatenation(); + _builder.append("foo "); + _builder.append(("dfdf" + Integer.valueOf(23))); + _builder.append(" bar"); + _builder.newLineIfNotEmpty(); + return _builder; + } + + private String multiLineString() { + StringConcatenation _builder = new StringConcatenation(); + _builder.append("test "); + _builder.newLine(); + _builder.append("bar"); + _builder.newLine(); + _builder.append("\t"); + CharSequence _other = this.other(); + _builder.append(_other, "\t"); + _builder.newLineIfNotEmpty(); + return _builder.toString(); + } + + public void assertEquals(final StringConcatenationClient c) { + final GeneratorNodeExtensions ext = new GeneratorNodeExtensions(); + final GeneratorNodeProcessor processor = new GeneratorNodeProcessor(); + final CompositeGeneratorNode root = new CompositeGeneratorNode(); + ext.appendTemplate(root, c); + final GeneratorNodeProcessor.Result result = processor.process(root); + final StringConcatenation expected = new StringConcatenation(); + expected.append(c); + Assert.assertEquals(expected.toString(), result.toString()); + } +} diff --git a/org.eclipse.xtext.tests/xtend-gen/org/eclipse/xtext/generator/trace/node/TracingSugarTest.java b/org.eclipse.xtext.tests/xtend-gen/org/eclipse/xtext/generator/trace/node/TracingSugarTest.java new file mode 100644 index 000000000..f879a52c0 --- /dev/null +++ b/org.eclipse.xtext.tests/xtend-gen/org/eclipse/xtext/generator/trace/node/TracingSugarTest.java @@ -0,0 +1,393 @@ +/** + * Copyright (c) 2017 TypeFox (https://typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.xtext.generator.trace.node; + +import com.google.inject.Inject; +import java.util.function.Function; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.xtend2.lib.StringConcatenation; +import org.eclipse.xtend2.lib.StringConcatenationClient; +import org.eclipse.xtext.generator.InMemoryFileSystemAccess; +import org.eclipse.xtext.generator.trace.AbstractTraceRegion; +import org.eclipse.xtext.generator.trace.ILocationData; +import org.eclipse.xtext.generator.trace.ITraceRegionProvider; +import org.eclipse.xtext.generator.trace.node.CompositeGeneratorNode; +import org.eclipse.xtext.generator.trace.node.IGeneratorNode; +import org.eclipse.xtext.generator.trace.node.Traced; +import org.eclipse.xtext.generator.trace.node.TracedAccessors; +import org.eclipse.xtext.generator.trace.node.TracingSugar; +import org.eclipse.xtext.linking.lazy.lazyLinking.LazyLinkingFactory; +import org.eclipse.xtext.linking.lazy.lazyLinking.Model; +import org.eclipse.xtext.linking.lazy.lazyLinking.Property; +import org.eclipse.xtext.linking.lazy.lazyLinking.Type; +import org.eclipse.xtext.linking.lazy.lazyLinking.UnresolvedProxyProperty; +import org.eclipse.xtext.linking.lazy.tests.LazyLinkingTestLanguageInjectorProvider; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.XtextRunner; +import org.eclipse.xtext.testing.util.ParseHelper; +import org.eclipse.xtext.xbase.lib.Exceptions; +import org.eclipse.xtext.xbase.lib.Extension; +import org.eclipse.xtext.xbase.lib.Functions.Function1; +import org.eclipse.xtext.xbase.lib.IterableExtensions; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * @author Sven Efftinge - Initial contribution and API + */ +@RunWith(XtextRunner.class) +@InjectWith(LazyLinkingTestLanguageInjectorProvider.class) +@SuppressWarnings("all") +public class TracingSugarTest { + @TracedAccessors(LazyLinkingFactory.class) + public static class MyExtensions extends TracingSugar { + /** + * manual implementation for unsupported multi cross reference + */ + public IGeneratorNode _type(final Property it, final Function1 provider) { + final ILocationData location = this.location(it, it.eClass().getEStructuralFeature("type"), 0); + final CompositeGeneratorNode result = this.trace(location); + this.append(result, provider.apply(IterableExtensions.head(it.getType()))); + return result; + } + + public IGeneratorNode _class(final Model target, final Function, String> stringProvider) { + EStructuralFeature feature = target.eClass().getEStructuralFeature("class"); + ILocationData location = this.location(target, feature, -1); + CompositeGeneratorNode trace = this.trace(location); + this.append(trace, stringProvider.apply(target.getClass())); + return trace; + } + + public IGeneratorNode _name(final Property target) { + EStructuralFeature feature = target.eClass().getEStructuralFeature("name"); + ILocationData location = this.location(target, feature, -1); + CompositeGeneratorNode trace = this.trace(location); + this.append(trace, target.getName()); + return trace; + } + + public IGeneratorNode _name(final Property target, final Function stringProvider) { + EStructuralFeature feature = target.eClass().getEStructuralFeature("name"); + ILocationData location = this.location(target, feature, -1); + CompositeGeneratorNode trace = this.trace(location); + this.append(trace, stringProvider.apply(target.getName())); + return trace; + } + + public IGeneratorNode _class(final Property target, final Function, String> stringProvider) { + EStructuralFeature feature = target.eClass().getEStructuralFeature("class"); + ILocationData location = this.location(target, feature, -1); + CompositeGeneratorNode trace = this.trace(location); + this.append(trace, stringProvider.apply(target.getClass())); + return trace; + } + + public IGeneratorNode _extends(final Type target, final Function stringProvider) { + EStructuralFeature feature = target.eClass().getEStructuralFeature("extends"); + ILocationData location = this.location(target, feature, -1); + CompositeGeneratorNode trace = this.trace(location); + this.append(trace, stringProvider.apply(target.getExtends())); + return trace; + } + + public IGeneratorNode _name(final Type target) { + EStructuralFeature feature = target.eClass().getEStructuralFeature("name"); + ILocationData location = this.location(target, feature, -1); + CompositeGeneratorNode trace = this.trace(location); + this.append(trace, target.getName()); + return trace; + } + + public IGeneratorNode _name(final Type target, final Function stringProvider) { + EStructuralFeature feature = target.eClass().getEStructuralFeature("name"); + ILocationData location = this.location(target, feature, -1); + CompositeGeneratorNode trace = this.trace(location); + this.append(trace, stringProvider.apply(target.getName())); + return trace; + } + + public IGeneratorNode _parentId(final Type target, final Function stringProvider) { + EStructuralFeature feature = target.eClass().getEStructuralFeature("parentId"); + ILocationData location = this.location(target, feature, -1); + CompositeGeneratorNode trace = this.trace(location); + this.append(trace, stringProvider.apply(target.getParentId())); + return trace; + } + + public IGeneratorNode _class(final Type target, final Function, String> stringProvider) { + EStructuralFeature feature = target.eClass().getEStructuralFeature("class"); + ILocationData location = this.location(target, feature, -1); + CompositeGeneratorNode trace = this.trace(location); + this.append(trace, stringProvider.apply(target.getClass())); + return trace; + } + + public IGeneratorNode _name(final UnresolvedProxyProperty target) { + EStructuralFeature feature = target.eClass().getEStructuralFeature("name"); + ILocationData location = this.location(target, feature, -1); + CompositeGeneratorNode trace = this.trace(location); + this.append(trace, target.getName()); + return trace; + } + + public IGeneratorNode _name(final UnresolvedProxyProperty target, final Function stringProvider) { + EStructuralFeature feature = target.eClass().getEStructuralFeature("name"); + ILocationData location = this.location(target, feature, -1); + CompositeGeneratorNode trace = this.trace(location); + this.append(trace, stringProvider.apply(target.getName())); + return trace; + } + + public IGeneratorNode _class(final UnresolvedProxyProperty target, final Function, String> stringProvider) { + EStructuralFeature feature = target.eClass().getEStructuralFeature("class"); + ILocationData location = this.location(target, feature, -1); + CompositeGeneratorNode trace = this.trace(location); + this.append(trace, stringProvider.apply(target.getClass())); + return trace; + } + } + + @Inject + @Extension + private TracingSugarTest.MyExtensions _myExtensions; + + @Inject + private ParseHelper parseHelper; + + private CharSequence generated; + + private InMemoryFileSystemAccess fsa = new InMemoryFileSystemAccess() { + @Override + public void generateFile(final String fileName, final CharSequence contents) { + TracingSugarTest.this.generated = contents; + super.generateFile(fileName, contents); + } + }; + + @Test + public void testCodeGeneration() { + try { + StringConcatenation _builder = new StringConcatenation(); + _builder.append("type String {}"); + _builder.newLine(); + _builder.append("type Foo {"); + _builder.newLine(); + _builder.append("\t"); + _builder.append("String name;"); + _builder.newLine(); + _builder.append("}"); + _builder.newLine(); + final Model root = this.parseHelper.parse(_builder); + StringConcatenationClient _client = new StringConcatenationClient() { + @Override + protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) { + { + EList _types = root.getTypes(); + for(final Type t : _types) { + IGeneratorNode __generateType = TracingSugarTest.this._generateType(t); + _builder.append(__generateType); + _builder.newLineIfNotEmpty(); + } + } + } + }; + this._myExtensions.generateTracedFile(this.fsa, "foo/bar.txt", root, _client); + StringConcatenation _builder_1 = new StringConcatenation(); + { + EList _types = root.getTypes(); + for(final Type t : _types) { + CharSequence _generateType = this.generateType(t); + _builder_1.append(_generateType); + _builder_1.newLineIfNotEmpty(); + } + } + Assert.assertEquals(_builder_1.toString(), this.generated.toString()); + final AbstractTraceRegion trace = ((ITraceRegionProvider) this.generated).getTraceRegion(); + StringConcatenation _builder_2 = new StringConcatenation(); + _builder_2.append("CompletableTraceRegion [myOffset=0, myLength=55] associations={"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("LocationData [TextRegionWithLineInformation [0:41][lineNumber=0, endLineNumber=3]][path=__synthetic0.lazylinkingtestlanguage]"); + _builder_2.newLine(); + _builder_2.append("} nestedRegions={"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("CompletableTraceRegion [myOffset=0, myLength=17] associations={"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("LocationData [TextRegionWithLineInformation [0:14][lineNumber=0, endLineNumber=0]][path=__synthetic0.lazylinkingtestlanguage]"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("} nestedRegions={"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("CompletableTraceRegion [myOffset=6, myLength=6] associations={"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("LocationData [TextRegionWithLineInformation [5:6][lineNumber=0, endLineNumber=0]][path=__synthetic0.lazylinkingtestlanguage]"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("}"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("}"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("CompletableTraceRegion [myOffset=17, myLength=38] associations={"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("LocationData [TextRegionWithLineInformation [15:26][lineNumber=1, endLineNumber=3]][path=__synthetic0.lazylinkingtestlanguage]"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("} nestedRegions={"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("CompletableTraceRegion [myOffset=23, myLength=3] associations={"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("LocationData [TextRegionWithLineInformation [20:3][lineNumber=1, endLineNumber=1]][path=__synthetic0.lazylinkingtestlanguage]"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("}"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("CompletableTraceRegion [myOffset=30, myLength=24] associations={"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("LocationData [TextRegionWithLineInformation [27:12][lineNumber=2, endLineNumber=2]][path=__synthetic0.lazylinkingtestlanguage]"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("} nestedRegions={"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("CompletableTraceRegion [myOffset=39, myLength=4] associations={"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("LocationData [TextRegionWithLineInformation [34:4][lineNumber=2, endLineNumber=2]][path=__synthetic0.lazylinkingtestlanguage]"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("}"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("CompletableTraceRegion [myOffset=46, myLength=6] associations={"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("LocationData [TextRegionWithLineInformation [27:6][lineNumber=2, endLineNumber=2]][path=__synthetic0.lazylinkingtestlanguage]"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("}"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("}"); + _builder_2.newLine(); + _builder_2.append(" "); + _builder_2.append("}"); + _builder_2.newLine(); + _builder_2.append("}"); + Assert.assertEquals(_builder_2.toString(), trace.toString()); + } catch (Throwable _e) { + throw Exceptions.sneakyThrow(_e); + } + } + + @Traced + public IGeneratorNode _generateType(final Type it) { + ILocationData _location = this._myExtensions.location(it); + CompositeGeneratorNode _traceNode = this._myExtensions.trace(_location); + this._myExtensions.appendTemplate(_traceNode, __generateType(it)); + return _traceNode; + } + + @Traced + public IGeneratorNode _generateProperty(final Property it) { + ILocationData _location = this._myExtensions.location(it); + CompositeGeneratorNode _traceNode = this._myExtensions.trace(_location); + this._myExtensions.appendTemplate(_traceNode, __generateProperty(it)); + return _traceNode; + } + + public CharSequence generateType(final Type it) { + StringConcatenation _builder = new StringConcatenation(); + _builder.append("Class "); + String _name = it.getName(); + _builder.append(_name); + _builder.append(" {"); + _builder.newLineIfNotEmpty(); + { + EList _properties = it.getProperties(); + for(final Property p : _properties) { + _builder.append("\t"); + CharSequence _generateProperty = this.generateProperty(p); + _builder.append(_generateProperty, "\t"); + _builder.newLineIfNotEmpty(); + } + } + _builder.append("}"); + _builder.newLine(); + return _builder; + } + + public CharSequence generateProperty(final Property it) { + StringConcatenation _builder = new StringConcatenation(); + _builder.append("Property "); + String _name = it.getName(); + _builder.append(_name); + _builder.append(" : "); + String _name_1 = IterableExtensions.head(it.getType()).getName(); + _builder.append(_name_1); + _builder.newLineIfNotEmpty(); + return _builder; + } + + public StringConcatenationClient __generateType(final Type it) { + StringConcatenationClient _client = new StringConcatenationClient() { + @Override + protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) { + _builder.append("Class "); + IGeneratorNode __name = TracingSugarTest.this._myExtensions._name(it); + _builder.append(__name); + _builder.append(" {"); + _builder.newLineIfNotEmpty(); + { + EList _properties = it.getProperties(); + for(final Property p : _properties) { + _builder.append("\t"); + IGeneratorNode __generateProperty = TracingSugarTest.this._generateProperty(p); + _builder.append(__generateProperty, "\t"); + _builder.newLineIfNotEmpty(); + } + } + _builder.append("}"); + _builder.newLine(); + } + }; + return _client; + } + + public StringConcatenationClient __generateProperty(final Property it) { + StringConcatenationClient _client = new StringConcatenationClient() { + @Override + protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) { + _builder.append("Property "); + IGeneratorNode __name = TracingSugarTest.this._myExtensions._name(it); + _builder.append(__name); + _builder.append(" : "); + final Function1 _function = (Type it_1) -> { + return it_1.getName(); + }; + IGeneratorNode __type = TracingSugarTest.this._myExtensions._type(it, _function); + _builder.append(__type); + _builder.newLineIfNotEmpty(); + } + }; + return _client; + } +} diff --git a/org.eclipse.xtext.xtext.wizard/src/org/eclipse/xtext/xtext/wizard/ParentProjectDescriptor.xtend b/org.eclipse.xtext.xtext.wizard/src/org/eclipse/xtext/xtext/wizard/ParentProjectDescriptor.xtend index 6da27c459..141b11be5 100644 --- a/org.eclipse.xtext.xtext.wizard/src/org/eclipse/xtext/xtext/wizard/ParentProjectDescriptor.xtend +++ b/org.eclipse.xtext.xtext.wizard/src/org/eclipse/xtext/xtext/wizard/ParentProjectDescriptor.xtend @@ -154,7 +154,7 @@ class ParentProjectDescriptor extends ProjectDescriptor { } sourceSets.all { - resources.exclude '**/*.g', '**/*.xtext', '**/*.mwe2', '**/*.xtend', '**/*._trace' + resources.exclude '**/*.g', '**/*.mwe2', '**/*.xtend', '**/*._trace' } «ELSE» sourceSets { @@ -174,7 +174,12 @@ class ParentProjectDescriptor extends ProjectDescriptor { jar { from('model') { into('model') - } + } + «IF config.sourceLayout != SourceLayout.PLAIN» + from(sourceSets.main.allSource) { + include '**/*.xtext' + } + «ENDIF» manifest { attributes 'Bundle-SymbolicName': project.name } diff --git a/org.eclipse.xtext.xtext.wizard/src/org/eclipse/xtext/xtext/wizard/RuntimeProjectDescriptor.xtend b/org.eclipse.xtext.xtext.wizard/src/org/eclipse/xtext/xtext/wizard/RuntimeProjectDescriptor.xtend index 054f54b47..5e5182efa 100644 --- a/org.eclipse.xtext.xtext.wizard/src/org/eclipse/xtext/xtext/wizard/RuntimeProjectDescriptor.xtend +++ b/org.eclipse.xtext.xtext.wizard/src/org/eclipse/xtext/xtext/wizard/RuntimeProjectDescriptor.xtend @@ -673,7 +673,7 @@ class RuntimeProjectDescriptor extends TestedProjectDescriptor { - + diff --git a/org.eclipse.xtext.xtext.wizard/xtend-gen/org/eclipse/xtext/xtext/wizard/ParentProjectDescriptor.java b/org.eclipse.xtext.xtext.wizard/xtend-gen/org/eclipse/xtext/xtext/wizard/ParentProjectDescriptor.java index 83972e874..197daecfa 100644 --- a/org.eclipse.xtext.xtext.wizard/xtend-gen/org/eclipse/xtext/xtext/wizard/ParentProjectDescriptor.java +++ b/org.eclipse.xtext.xtext.wizard/xtend-gen/org/eclipse/xtext/xtext/wizard/ParentProjectDescriptor.java @@ -398,7 +398,7 @@ public class ParentProjectDescriptor extends ProjectDescriptor { _builder.append("sourceSets.all {"); _builder.newLine(); _builder.append("\t"); - _builder.append("resources.exclude \'**/*.g\', \'**/*.xtext\', \'**/*.mwe2\', \'**/*.xtend\', \'**/*._trace\'"); + _builder.append("resources.exclude \'**/*.g\', \'**/*.mwe2\', \'**/*.xtend\', \'**/*._trace\'"); _builder.newLine(); _builder.append("}"); _builder.newLine(); @@ -481,6 +481,22 @@ public class ParentProjectDescriptor extends ProjectDescriptor { _builder.append("\t"); _builder.append("}"); _builder.newLine(); + { + SourceLayout _sourceLayout_1 = this.getConfig().getSourceLayout(); + boolean _notEquals = (!Objects.equal(_sourceLayout_1, SourceLayout.PLAIN)); + if (_notEquals) { + _builder.append("\t"); + _builder.append("from(sourceSets.main.allSource) {"); + _builder.newLine(); + _builder.append("\t"); + _builder.append("\t"); + _builder.append("include \'**/*.xtext\'"); + _builder.newLine(); + _builder.append("\t"); + _builder.append("}"); + _builder.newLine(); + } + } _builder.append("\t"); _builder.append("manifest {"); _builder.newLine(); diff --git a/org.eclipse.xtext.xtext.wizard/xtend-gen/org/eclipse/xtext/xtext/wizard/RuntimeProjectDescriptor.java b/org.eclipse.xtext.xtext.wizard/xtend-gen/org/eclipse/xtext/xtext/wizard/RuntimeProjectDescriptor.java index 0e774ba25..f930c9efc 100644 --- a/org.eclipse.xtext.xtext.wizard/xtend-gen/org/eclipse/xtext/xtext/wizard/RuntimeProjectDescriptor.java +++ b/org.eclipse.xtext.xtext.wizard/xtend-gen/org/eclipse/xtext/xtext/wizard/RuntimeProjectDescriptor.java @@ -1820,7 +1820,7 @@ public class RuntimeProjectDescriptor extends TestedProjectDescriptor { _builder.newLine(); _builder.append(""); _builder.newLine(); - _builder.append(""); + _builder.append(""); _builder.newLine(); _builder.append(""); _builder.newLine(); diff --git a/org.eclipse.xtext/META-INF/MANIFEST.MF b/org.eclipse.xtext/META-INF/MANIFEST.MF index 5599b849d..881364ce9 100644 --- a/org.eclipse.xtext/META-INF/MANIFEST.MF +++ b/org.eclipse.xtext/META-INF/MANIFEST.MF @@ -34,6 +34,7 @@ Export-Package: org.eclipse.xtext, org.eclipse.xtext.generator, org.eclipse.xtext.generator.trace;x-friends:="org.eclipse.xtext.xbase.ui,org.eclipse.xtend.ide", org.eclipse.xtext.generator.trace.internal;x-friends:="org.eclipse.xtext.ui,org.eclipse.xtext.xbase.ui", + org.eclipse.xtext.generator.trace.node;x-friends:="org.eclipse.xtext.tests", org.eclipse.xtext.grammaranalysis;x-internal:=true, org.eclipse.xtext.grammaranalysis.impl;x-friends:="org.eclipse.xtext.junit4,org.eclipse.xtext.xtext.generator", org.eclipse.xtext.impl, diff --git a/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/CompositeGeneratorNode.xtend b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/CompositeGeneratorNode.xtend new file mode 100644 index 000000000..8236afac4 --- /dev/null +++ b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/CompositeGeneratorNode.xtend @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.generator.trace.node + +import java.util.List +import org.eclipse.xtend.lib.annotations.Accessors + +/** + * @author Sven Efftinge - Initial contribution and API + */ +@Accessors class CompositeGeneratorNode implements IGeneratorNode { + + val List children = newArrayList + + override toString() ''' + «class.simpleName» { + «FOR c: children» + «c.toString» + «ENDFOR» + } + ''' + +} \ No newline at end of file diff --git a/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/GeneratorNodeExtensions.xtend b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/GeneratorNodeExtensions.xtend new file mode 100644 index 000000000..72cd3d4b5 --- /dev/null +++ b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/GeneratorNodeExtensions.xtend @@ -0,0 +1,117 @@ +/******************************************************************************* + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.generator.trace.node + +import com.google.inject.Inject +import org.eclipse.xtend2.lib.StringConcatenationClient +import org.eclipse.xtext.generator.trace.ILocationData + +/** + * A builder API to create generator node trees + * + * @author Sven Efftinge - Initial contribution and API + */ +class GeneratorNodeExtensions { + + @Inject GeneratorWhiteSpaceConfig wsConfig = new GeneratorWhiteSpaceConfig + + /** + * @return a root trace node for the given location + */ + def CompositeGeneratorNode trace(ILocationData data) { + val result = new TraceNode(data) + return result + } + + /** + * @return a trace node for the given location, appended as a child on the given parent + */ + def CompositeGeneratorNode trace(CompositeGeneratorNode parent, ILocationData data) { + val result = new TraceNode(data) + parent.children += result + return result + } + + /** + * @return an indentation node, using the default indentation string, appended as a child on the given parent + */ + def CompositeGeneratorNode indent(CompositeGeneratorNode parent) { + return indent(parent, wsConfig.indentationString) + } + + /** + * Appends the indentation string at the current position of the parent and adds a new composite node, indicating the same indentation for + * subsequent lines. + * + * @return an indentation node, using the given indentString, appended as a child on the given parent + */ + def CompositeGeneratorNode indent(CompositeGeneratorNode parent, String indentString) { + val text = new TextNode(indentString) + parent.children += text + val result = new IndentNode(indentString) + parent.children += result + return result + } + + /** + * Appends a line separator node to the given parent. + * + * @return the given parent node + */ + def CompositeGeneratorNode appendNewLine(CompositeGeneratorNode parent) { + parent.children += new NewLineNode(wsConfig.lineDelimiter, false) + return parent + } + + /** + * Appends a line separator node to the given parent. + * + * @return the given parent node + */ + def CompositeGeneratorNode appendNewLine(CompositeGeneratorNode parent, String lineSeparator) { + parent.children += new NewLineNode(lineSeparator, false) + return parent + } + + /** + * Appends a line separator node that will only be effective if the current line contains non-whitespace text. + * + * @return the given parent node + */ + def CompositeGeneratorNode appendNewLineIfNotEmpty(CompositeGeneratorNode parent) { + parent.children += new NewLineNode(wsConfig.lineDelimiter, true) + return parent + } + /** + * Creates a text node containing the toString() representation of the given object and + * appends it to the given parent node. + * + * @return the given parent node + */ + def CompositeGeneratorNode append(CompositeGeneratorNode parent, Object object) { + if (object !== null) { + parent.children += new TextNode(object.toString) + } + return parent + } + + /** + * Creates a template node for the given templateString and appends it to the given parent node. + * + * Templates are translated to generator node trees and expressions in templates can be of type IGeneratorNode. + * + * @return the given parent node + */ + def CompositeGeneratorNode appendTemplate(CompositeGeneratorNode parent, StringConcatenationClient templateString) { + val proc = new TemplateNode(templateString, this) + parent.children += proc + return parent + } + + +} \ No newline at end of file diff --git a/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/GeneratorNodeProcessor.xtend b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/GeneratorNodeProcessor.xtend new file mode 100644 index 000000000..c99641d4e --- /dev/null +++ b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/GeneratorNodeProcessor.xtend @@ -0,0 +1,179 @@ +/******************************************************************************* + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.generator.trace.node + +import java.util.ArrayDeque +import java.util.Deque +import java.util.List +import org.eclipse.xtend.lib.annotations.Accessors +import org.eclipse.xtend.lib.annotations.Data +import org.eclipse.xtend.lib.annotations.Delegate +import org.eclipse.xtext.generator.trace.AbstractStatefulTraceRegion +import org.eclipse.xtext.generator.trace.AbstractTraceRegion +import org.eclipse.xtext.generator.trace.ILocationData +import org.eclipse.xtext.generator.trace.ITraceRegionProvider +import org.eclipse.xtext.generator.trace.TraceNotFoundException +import org.eclipse.xtext.util.ITextRegionWithLineInformation +import org.eclipse.xtext.util.TextRegionWithLineInformation + +/** + * @author Sven Efftinge - Initial contribution and API + */ +class GeneratorNodeProcessor { + + @Data static class Result implements CharSequence, ITraceRegionProvider { + @Delegate CharSequence contents + AbstractTraceRegion traceRegion + + override getTraceRegion() throws TraceNotFoundException { + return traceRegion + } + + override toString() { + contents.toString() + } + } + + @Accessors protected static class Context { + List lines + Deque currentIndents + boolean pendingIndent + AbstractTraceRegion currentRegion = null + + def StringBuilder currentLine() { + return lines.get(currentLineNumber); + } + + def int contentLength() { + val contentLength = lines.fold(0) [ $0 + $1.length ] + if (pendingIndent) { + return contentLength + currentIndents.fold(0) [ $0 + $1.length ] + } else { + return contentLength + } + } + + def int currentLineNumber() { + return lines.size - 1 + } + } + + def Result process(IGeneratorNode root) { + val ctx = new Context => [ + lines = newArrayList(new StringBuilder()) + currentIndents = new ArrayDeque() + pendingIndent = true + ] + doProcess(root, ctx) + return new Result(ctx.lines.join, ctx.currentRegion); + } + + /** + * Indent nodes apply indentation between newline and content of its children. + */ + protected def dispatch void doProcess(IndentNode node, Context ctx) { + try { + ctx.currentIndents.push(node.indentationString); + doProcessChildren(node, ctx); + } finally { + ctx.currentIndents.pop + } + } + + protected def dispatch void doProcess(NewLineNode node, Context ctx) { + val trimmedLine = ctx.currentLine.toString.trim + if (node.ifNotEmpty && trimmedLine.empty) { + ctx.lines.set(ctx.currentLineNumber, new StringBuilder()) + return + } + ctx.currentLine.append(node.lineDelimiter) + ctx.lines.add(new StringBuilder) + ctx.pendingIndent = true + } + + protected def dispatch void doProcess(TextNode node, Context ctx) { + val txt = node.text.toString + if (txt.empty) { + return; + } + if (ctx.pendingIndent) { + val indentString = new StringBuilder() + for (indentationString : ctx.currentIndents) { + indentString.append(indentationString) + } + ctx.currentLine.insert(0, indentString) + ctx.pendingIndent = false + } + ctx.currentLine.append(node.text) + } + + protected def dispatch void doProcess(CompositeGeneratorNode node, Context ctx) { + doProcessChildren(node, ctx) + } + + protected def dispatch void doProcess(TraceNode node, Context ctx) { + val beforeRegion = ctx.currentRegion + val newRegion = new CompletableTraceRegion(false, node.sourceLocation, beforeRegion) + val offset = ctx.contentLength + val startLineNumber = ctx.currentLineNumber; + try { + ctx.currentRegion = newRegion + doProcessChildren(node, ctx); + } finally { + if (beforeRegion !== null) + ctx.currentRegion = beforeRegion + newRegion.complete(offset, ctx.contentLength - offset, startLineNumber, ctx.currentLineNumber) + } + } + + protected def void doProcessChildren(CompositeGeneratorNode node, Context ctx) { + for (child : node.children) { + doProcess(child, ctx) + } + } + + /** + * Used to avoid multi-pass processing, when constructing a trace region tree. + * + * @author Sven Efftinge - Initial contribution and API + */ + static class CompletableTraceRegion extends AbstractStatefulTraceRegion { + + static class CompletableTextRegion implements ITextRegionWithLineInformation { + ITextRegionWithLineInformation delegate + + @Delegate def ITextRegionWithLineInformation getDelegate() { + if (delegate === null) { + throw new IllegalStateException("region not completed") + } + return delegate + } + } + + CompletableTextRegion region + + new(boolean useForDebugging, ILocationData associatedLocation, AbstractTraceRegion parent) { + this(new CompletableTextRegion(), useForDebugging, associatedLocation, parent) + } + + protected new(CompletableTextRegion region, boolean useForDebugging, ILocationData associatedLocation, + AbstractTraceRegion parent) { + super(region, useForDebugging, associatedLocation, parent) + this.region = region + } + + def void complete(int offset, int length, int startLine, int endLine) { + this.region.delegate = new TextRegionWithLineInformation(offset, length, startLine, endLine) + } + + override protected isConsistentWithParent() { + return true + } + + } +} diff --git a/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/GeneratorWhiteSpaceConfig.xtend b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/GeneratorWhiteSpaceConfig.xtend new file mode 100644 index 000000000..7551d3aad --- /dev/null +++ b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/GeneratorWhiteSpaceConfig.xtend @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.generator.trace.node + +/** + * @author Sven Efftinge - Initial contribution and API + */ +class GeneratorWhiteSpaceConfig { + + def String getIndentationString() { + ' ' + } + + def String getLineDelimiter() { + '\n' + } +} \ No newline at end of file diff --git a/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/IGeneratorNode.xtend b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/IGeneratorNode.xtend new file mode 100644 index 000000000..c6cbbb079 --- /dev/null +++ b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/IGeneratorNode.xtend @@ -0,0 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.generator.trace.node + +/** + * + * A node in the code generator graph + * + * @author Sven Efftinge - Initial contribution and API + */ +interface IGeneratorNode { + +} \ No newline at end of file diff --git a/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/IndentNode.xtend b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/IndentNode.xtend new file mode 100644 index 000000000..df50c9f89 --- /dev/null +++ b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/IndentNode.xtend @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.generator.trace.node + +import org.eclipse.xtend.lib.annotations.Accessors +import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor + +/** + * @author Sven Efftinge - Initial contribution and API + */ +@FinalFieldsConstructor class IndentNode extends CompositeGeneratorNode { + + @Accessors val String indentationString +} \ No newline at end of file diff --git a/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/NewLineNode.xtend b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/NewLineNode.xtend new file mode 100644 index 000000000..4c8a2a741 --- /dev/null +++ b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/NewLineNode.xtend @@ -0,0 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.generator.trace.node + +import org.eclipse.xtend.lib.annotations.Data + +/** + * @author Sven Efftinge - Initial contribution and API + */ +@Data class NewLineNode implements IGeneratorNode { + String lineDelimiter + boolean ifNotEmpty +} \ No newline at end of file diff --git a/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/TemplateNode.xtend b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/TemplateNode.xtend new file mode 100644 index 000000000..c160128ea --- /dev/null +++ b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/TemplateNode.xtend @@ -0,0 +1,115 @@ +/******************************************************************************* + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.generator.trace.node + +import com.google.common.base.Splitter +import com.google.common.collect.Iterables +import java.util.regex.Pattern +import org.eclipse.xtend2.lib.StringConcatenationClient +import org.eclipse.xtend2.lib.StringConcatenationClient.TargetStringConcatenation + +/** + * @author Sven Efftinge - Initial contribution and API + */ +class TemplateNode extends CompositeGeneratorNode implements TargetStringConcatenation { + + val GeneratorNodeExtensions nodeFactory + + new(StringConcatenationClient contents, GeneratorNodeExtensions nodeFactory) { + this.nodeFactory = nodeFactory + StringConcatenationClient.appendTo(contents, this) + } + + CompositeGeneratorNode currentParent = this + boolean isEmptyLine = true; + + override append(Object object, String indentation) { + if (indentation.length > 0) { + val before = currentParent + try { + currentParent = new IndentNode(indentation) + before.children += currentParent + append(object) + } finally { + currentParent = before + } + } else { + append(object) + } + } + + val lineSplitter = Splitter.on(Pattern.compile("\\R")) + + override append(Object object) { + switch object { + StringConcatenationClient: + nodeFactory.appendTemplate(currentParent, object) + IGeneratorNode: { + currentParent.children += object + } + default: { + val str = object.toString + val iter = lineSplitter.split(str).iterator + while (iter.hasNext) { + val segment = iter.next + if (!segment.isEmpty) + isEmptyLine = false + nodeFactory.append(currentParent, segment) + if (iter.hasNext) { + newLine + } + } + } + } + } + + protected def Iterable leafsBackwards(IGeneratorNode it) { + switch it { + CompositeGeneratorNode : { + children.reverseView.map[leafsBackwards].reduce[p1, p2| Iterables.concat(p1, p2)] + } + default : { + #[it] + } + } + } + + override appendImmediate(Object object, String indentation) { + for (var i = currentParent.children.size; i >= 0; i--) { + val node = currentParent.children.get(i) + if (node instanceof TextNode) { + if (node.text.toString.trim.length === 0) { + currentParent.children.remove(i) + } + } + } + append(object, indentation) + } + + override newLine() { + this.nodeFactory.appendNewLine(currentParent) + } + + override newLineIfNotEmpty() { + this.nodeFactory.appendNewLineIfNotEmpty(currentParent) + } + + // + override charAt(int index) { + throw new UnsupportedOperationException("TODO: auto-generated method stub") + } + + override length() { + throw new UnsupportedOperationException("TODO: auto-generated method stub") + } + + override subSequence(int start, int end) { + throw new UnsupportedOperationException("TODO: auto-generated method stub") + } + +} diff --git a/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/TextNode.xtend b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/TextNode.xtend new file mode 100644 index 000000000..f15f04e27 --- /dev/null +++ b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/TextNode.xtend @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.generator.trace.node + +import org.eclipse.xtend.lib.annotations.Data + +/** + * @author Sven Efftinge - Initial contribution and API + */ +@Data class TextNode implements IGeneratorNode { + CharSequence text +} \ No newline at end of file diff --git a/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/TraceNode.xtend b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/TraceNode.xtend new file mode 100644 index 000000000..c9b814fab --- /dev/null +++ b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/TraceNode.xtend @@ -0,0 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2017 TypeFox (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.generator.trace.node + +import org.eclipse.xtend.lib.annotations.Accessors +import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor +import org.eclipse.xtext.generator.trace.ILocationData + +/** + * @author Sven Efftinge - initial contribution and API + */ +@FinalFieldsConstructor class TraceNode extends CompositeGeneratorNode { + + @Accessors final ILocationData sourceLocation + +} \ No newline at end of file diff --git a/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/Traced.xtend b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/Traced.xtend new file mode 100644 index 000000000..433756d24 --- /dev/null +++ b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/Traced.xtend @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.generator.trace.node + +import org.eclipse.emf.ecore.EObject +import org.eclipse.xtend.lib.macro.AbstractMethodProcessor +import org.eclipse.xtend.lib.macro.Active +import org.eclipse.xtend.lib.macro.TransformationContext +import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration +import org.eclipse.xtend.lib.macro.declaration.MutableMethodDeclaration +import org.eclipse.xtend2.lib.StringConcatenationClient +import org.eclipse.xtext.generator.trace.ILocationData + +/** + * @author Sven Efftinge - Initial contribution and API + */ +@Active(TracedProcessor) +annotation Traced { + String tracingSugarFieldName = '_traceExtensions' +} + +class TracedProcessor extends AbstractMethodProcessor { + + override doTransform(MutableMethodDeclaration annotatedMethod, extension TransformationContext context) { + val traceSugar = TracingSugar.newTypeReference + val templateClient = StringConcatenationClient.newTypeReference + val nodeType = IGeneratorNode.newTypeReference + val eobjectType = EObject.newTypeReference + + val clazz = annotatedMethod.declaringType as MutableClassDeclaration + val field = clazz.declaredFields.findFirst[ + traceSugar.isAssignableFrom(type) + ] + if (field === null) { + annotatedMethod.addError('''@«Traced.simpleName» methods can only declared in a class with a field of type «TracingSugar»''') + return; + } + val traceParam = annotatedMethod.parameters.findFirst[eobjectType.isAssignableFrom(type)] + if (traceParam === null) { + annotatedMethod.addError('''@«Traced.simpleName» methods need at least one parameter of type «EObject».''') + return; + } + clazz.addMethod('_'+annotatedMethod.simpleName) [ + for (p : annotatedMethod.parameters) { + addParameter(p.simpleName, p.type) + } + returnType = templateClient + body = annotatedMethod.body + ] + annotatedMethod.returnType = nodeType + annotatedMethod.body = ''' + «ILocationData» _location = this.«field.simpleName».location(«traceParam.simpleName»); + «CompositeGeneratorNode» _traceNode = this.«field.simpleName».trace(_location); + this.«field.simpleName».appendTemplate(_traceNode, _«annotatedMethod.simpleName»(«annotatedMethod.parameters.join(',')[simpleName]»)); + return _traceNode; + ''' + } + +} \ No newline at end of file diff --git a/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/TracedAccessors.xtend b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/TracedAccessors.xtend new file mode 100644 index 000000000..a53007179 --- /dev/null +++ b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/TracedAccessors.xtend @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.generator.trace.node + +import java.util.function.Function +import org.eclipse.emf.ecore.EFactory +import org.eclipse.emf.ecore.EStructuralFeature +import org.eclipse.xtend.lib.macro.AbstractClassProcessor +import org.eclipse.xtend.lib.macro.Active +import org.eclipse.xtend.lib.macro.TransformationContext +import org.eclipse.xtend.lib.macro.declaration.InterfaceDeclaration +import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration +import org.eclipse.xtend.lib.macro.declaration.ResolvedMethod +import org.eclipse.xtext.generator.trace.ILocationData + +/** + * @author Sven Efftinge - Initial contribution and API + */ +@Active(TracedAccessorsProcessor) +annotation TracedAccessors { + Class[] value +} + +class TracedAccessorsProcessor extends AbstractClassProcessor { + + override doTransform(MutableClassDeclaration annotatedClass, extension TransformationContext context) { + annotatedClass.extendedClass = TracingSugar.newTypeReference() + val iterableType = Iterable.newTypeReference(newWildcardTypeReference) + val annotationType = TracedAccessors.newTypeReference + val factories = annotatedClass.findAnnotation(annotationType.type)?.getClassArrayValue("value") + if (factories === null) { + return; + } + for (f : factories.map[type].filter(InterfaceDeclaration)) { + for (t: f.declaredMethods.filter[simpleName.startsWith('create') && parameters.empty].map[returnType]) { + for (getter : t.allResolvedMethods.filter[ isSupportedGetter].filter[!iterableType.isAssignableFrom(declaration.returnType)]) { + val rt = getter.resolvedReturnType + if (TYPES_WITH_GOOD_TO_STRING.contains(rt.type.simpleName.toLowerCase)) { + annotatedClass.addMethod(getter.tracerName) [ + returnType = IGeneratorNode.newTypeReference() + addParameter('target', t) + body = ''' + «EStructuralFeature» feature = target.eClass().getEStructuralFeature("«getter.featureName»"); + «ILocationData» location = this.location(target, feature, -1); + «CompositeGeneratorNode» trace = this.trace(location); + this.append(trace, target.«getter.declaration.simpleName»()); + return trace; + ''' + ] + } + annotatedClass.addMethod(getter.tracerName) [ + returnType = IGeneratorNode.newTypeReference + addParameter('target', t) + val stringProvider = Function.newTypeReference(rt, string) + addParameter('stringProvider', stringProvider) + body = ''' + «EStructuralFeature» feature = target.eClass().getEStructuralFeature("«getter.featureName»"); + «ILocationData» location = this.location(target, feature, -1); + «CompositeGeneratorNode» trace = this.trace(location); + this.append(trace, stringProvider.apply(target.«getter.declaration.simpleName»())); + return trace; + ''' + ] + } + } + } + } + + def String tracerName(ResolvedMethod m) { + '_'+m.featureName + } + + def String featureName(ResolvedMethod m) { + val n = m.declaration.simpleName + val skip = if (n.startsWith('get')) 3 else 2 + m.declaration.simpleName.substring(skip).toFirstLower + } + + static val TYPES_WITH_GOOD_TO_STRING = #{'string','boolean','int','long','integer'} + + def boolean isSupportedGetter(ResolvedMethod it) { + if (!declaration.parameters.empty) + return false + if (declaration.static) + return false + val n = it.declaration.simpleName + return n.startsWith('get') || n.startsWith('is') + } +} \ No newline at end of file diff --git a/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/TracingSugar.xtend b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/TracingSugar.xtend new file mode 100644 index 000000000..b2e995d17 --- /dev/null +++ b/org.eclipse.xtext/src/org/eclipse/xtext/generator/trace/node/TracingSugar.xtend @@ -0,0 +1,79 @@ +package org.eclipse.xtext.generator.trace.node + +import com.google.inject.Inject +import org.eclipse.emf.ecore.EObject +import org.eclipse.emf.ecore.EStructuralFeature +import org.eclipse.xtend2.lib.StringConcatenationClient +import org.eclipse.xtext.generator.IFileSystemAccess2 +import org.eclipse.xtext.generator.trace.ILocationData +import org.eclipse.xtext.generator.trace.ITraceURIConverter +import org.eclipse.xtext.generator.trace.LocationData +import org.eclipse.xtext.resource.ILocationInFileProvider +import org.eclipse.xtext.util.ITextRegionWithLineInformation + +/** + * Some additional sugar extension to + * - create generator nodes for EObjects + * - create ILocationData for EObjects. + * - enhance FileSystemAccess for tracing + */ +class TracingSugar extends GeneratorNodeExtensions { + + @Inject protected ILocationInFileProvider locationProvider + @Inject protected ITraceURIConverter traceURIConverter + @Inject protected GeneratorWhiteSpaceConfig whiteSpaceConfig + @Inject protected GeneratorNodeProcessor processor + + /** + * Convenience extension, to generate traced code. + */ + def void generateTracedFile(IFileSystemAccess2 fsa, String path, EObject rootTrace, StringConcatenationClient code) { + val node = trace(rootTrace, code) + generateTracedFile(fsa, path, node); + } + + /** + * Use to generate a file based on generator node. + */ + def void generateTracedFile(IFileSystemAccess2 fsa, String path, CompositeGeneratorNode rootNode) { + val result = processor.process(rootNode); + fsa.generateFile(path, result) + } + + /** + * Convenience shorthand for obj.location.trace + */ + def CompositeGeneratorNode trace(EObject obj) { + return obj.location.trace; + } + + /** + * Convenience shorthand for obj.trace.appendTemplate('''some template''') + */ + def CompositeGeneratorNode trace(EObject obj, StringConcatenationClient code) { + return obj.trace.appendTemplate(code) + } + + /** + * @return ILocationData covering the fullTextRegion of the given EObject. + */ + def ILocationData location(EObject obj) { + val region = locationProvider.getFullTextRegion(obj) + val uri = traceURIConverter.getURIForTrace(obj.eResource) + return new LocationData(region as ITextRegionWithLineInformation, uri); + } + + /** + * @param obj the EObject containing the feature + * @param feature the EStructuralFeature to trace + * @param idx the index of the value to trace, in case the feature contains a list, should be -1 otherwise. + * + * @return ILocationData covering the fullTextRegion of the given feature in the given EObject. + */ + def ILocationData location(EObject obj, EStructuralFeature feature, int idx) { + val region = locationProvider.getFullTextRegion(obj, feature, idx) + val uri = traceURIConverter.getURIForTrace(obj.eResource) + return new LocationData(region as ITextRegionWithLineInformation, uri); + } + +} diff --git a/org.eclipse.xtext/src/org/eclipse/xtext/resource/containers/ContainerState.java b/org.eclipse.xtext/src/org/eclipse/xtext/resource/containers/ContainerState.java index bae31c3aa..bbed26aab 100644 --- a/org.eclipse.xtext/src/org/eclipse/xtext/resource/containers/ContainerState.java +++ b/org.eclipse.xtext/src/org/eclipse/xtext/resource/containers/ContainerState.java @@ -27,7 +27,7 @@ class ContainerState implements IContainerState { @Override public boolean contains(URI uri) { - return getContents().contains(uri); + return globalState.containsURI(root, uri); } @Override diff --git a/org.eclipse.xtext/src/org/eclipse/xtext/resource/containers/DelegatingIAllContainerAdapter.java b/org.eclipse.xtext/src/org/eclipse/xtext/resource/containers/DelegatingIAllContainerAdapter.java index 6a7046857..0558830b4 100644 --- a/org.eclipse.xtext/src/org/eclipse/xtext/resource/containers/DelegatingIAllContainerAdapter.java +++ b/org.eclipse.xtext/src/org/eclipse/xtext/resource/containers/DelegatingIAllContainerAdapter.java @@ -39,6 +39,11 @@ public class DelegatingIAllContainerAdapter extends AdapterImpl implements IAllC public Collection getContainedURIs(String containerHandle) { return delegate.getContainedURIs(containerHandle); } + + @Override + public boolean containsURI(String containerHandle, URI candidateURI) { + return delegate.containsURI(containerHandle, candidateURI); + } @Override public String getContainerHandle(URI uri) { diff --git a/org.eclipse.xtext/src/org/eclipse/xtext/resource/containers/FlatResourceSetBasedAllContainersState.java b/org.eclipse.xtext/src/org/eclipse/xtext/resource/containers/FlatResourceSetBasedAllContainersState.java index 2578ab076..cacad8e91 100644 --- a/org.eclipse.xtext/src/org/eclipse/xtext/resource/containers/FlatResourceSetBasedAllContainersState.java +++ b/org.eclipse.xtext/src/org/eclipse/xtext/resource/containers/FlatResourceSetBasedAllContainersState.java @@ -1,6 +1,7 @@ package org.eclipse.xtext.resource.containers; import static com.google.common.collect.Lists.*; +import static org.eclipse.xtext.resource.impl.ResourceDescriptionsData.ResourceSetAdapter.*; import java.util.Collection; import java.util.Collections; @@ -43,12 +44,11 @@ public class FlatResourceSetBasedAllContainersState extends AdapterImpl implemen if (!HANDLE.equals(containerHandle)) return Collections.emptySet(); if (resourceSet instanceof XtextResourceSet) { - XtextResourceSet xtextResourceSet = (XtextResourceSet) resourceSet; - ResourceDescriptionsData descriptionsData = ResourceDescriptionsData.ResourceSetAdapter.findResourceDescriptionsData(resourceSet); + ResourceDescriptionsData descriptionsData = findResourceDescriptionsData(resourceSet); if (descriptionsData != null) { return descriptionsData.getAllURIs(); } - return newArrayList(xtextResourceSet.getNormalizationMap().values()); + return newArrayList(((XtextResourceSet) resourceSet).getNormalizationMap().values()); } List uris = Lists.newArrayListWithCapacity(resourceSet.getResources().size()); URIConverter uriConverter = resourceSet.getURIConverter(); @@ -56,6 +56,33 @@ public class FlatResourceSetBasedAllContainersState extends AdapterImpl implemen uris.add(uriConverter.normalize(r.getURI())); return uris; } + + @Override + public boolean containsURI(String containerHandle, URI candidateURI) { + if (!HANDLE.equals(containerHandle)) + return false; + if (resourceSet instanceof XtextResourceSet) { + ResourceDescriptionsData descriptionsData = findResourceDescriptionsData(resourceSet); + if (descriptionsData != null) { + return descriptionsData.getResourceDescription(candidateURI) != null; + } + Collection allUris = ((XtextResourceSet) resourceSet).getNormalizationMap().values(); + for (URI uri : allUris) { + if (uri.equals(candidateURI)) { + return true; + } + } + return false; + } + URIConverter uriConverter = resourceSet.getURIConverter(); + for (Resource r : resourceSet.getResources()) { + URI normalized = uriConverter.normalize(r.getURI()); + if (normalized.equals(candidateURI)) { + return true; + } + } + return false; + } @Override public String getContainerHandle(URI uri) { diff --git a/org.eclipse.xtext/src/org/eclipse/xtext/resource/containers/IAllContainersState.java b/org.eclipse.xtext/src/org/eclipse/xtext/resource/containers/IAllContainersState.java index cdcc4385c..9a9d48998 100644 --- a/org.eclipse.xtext/src/org/eclipse/xtext/resource/containers/IAllContainersState.java +++ b/org.eclipse.xtext/src/org/eclipse/xtext/resource/containers/IAllContainersState.java @@ -26,6 +26,10 @@ public interface IAllContainersState { String getContainerHandle(URI uri); + default boolean containsURI(String containerHandle, URI candidateURI) { + return getContainedURIs(containerHandle).contains(candidateURI); + } + interface Provider { IAllContainersState get(IResourceDescriptions context); } diff --git a/org.eclipse.xtext/src/org/eclipse/xtext/resource/containers/LiveShadowedAllContainerState.java b/org.eclipse.xtext/src/org/eclipse/xtext/resource/containers/LiveShadowedAllContainerState.java index 07f9780ff..9e35423c2 100644 --- a/org.eclipse.xtext/src/org/eclipse/xtext/resource/containers/LiveShadowedAllContainerState.java +++ b/org.eclipse.xtext/src/org/eclipse/xtext/resource/containers/LiveShadowedAllContainerState.java @@ -59,6 +59,14 @@ public class LiveShadowedAllContainerState implements IAllContainersState { result.addAll(globalState.getContainedURIs(containerHandle)); return result; } + + @Override + public boolean containsURI(String containerHandle, URI candidateURI) { + if(localDescriptions.getResourceDescription(candidateURI) != null) { + return true; + } + return globalState.containsURI(containerHandle, candidateURI); + } @Override public String getContainerHandle(URI uri) { diff --git a/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/CompositeGeneratorNode.java b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/CompositeGeneratorNode.java new file mode 100644 index 000000000..783711c9e --- /dev/null +++ b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/CompositeGeneratorNode.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.xtext.generator.trace.node; + +import java.util.List; +import org.eclipse.xtend.lib.annotations.Accessors; +import org.eclipse.xtend2.lib.StringConcatenation; +import org.eclipse.xtext.generator.trace.node.IGeneratorNode; +import org.eclipse.xtext.xbase.lib.CollectionLiterals; +import org.eclipse.xtext.xbase.lib.Pure; + +/** + * @author Sven Efftinge - Initial contribution and API + */ +@Accessors +@SuppressWarnings("all") +public class CompositeGeneratorNode implements IGeneratorNode { + private final List children = CollectionLiterals.newArrayList(); + + @Override + public String toString() { + StringConcatenation _builder = new StringConcatenation(); + String _simpleName = this.getClass().getSimpleName(); + _builder.append(_simpleName); + _builder.append(" {"); + _builder.newLineIfNotEmpty(); + { + for(final IGeneratorNode c : this.children) { + _builder.append("\t"); + String _string = c.toString(); + _builder.append(_string, "\t"); + _builder.newLineIfNotEmpty(); + } + } + _builder.append("}"); + _builder.newLine(); + return _builder.toString(); + } + + @Pure + public List getChildren() { + return this.children; + } +} diff --git a/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/GeneratorNodeExtensions.java b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/GeneratorNodeExtensions.java new file mode 100644 index 000000000..0d9139216 --- /dev/null +++ b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/GeneratorNodeExtensions.java @@ -0,0 +1,141 @@ +/** + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.xtext.generator.trace.node; + +import com.google.inject.Inject; +import java.util.List; +import org.eclipse.xtend2.lib.StringConcatenationClient; +import org.eclipse.xtext.generator.trace.ILocationData; +import org.eclipse.xtext.generator.trace.node.CompositeGeneratorNode; +import org.eclipse.xtext.generator.trace.node.GeneratorWhiteSpaceConfig; +import org.eclipse.xtext.generator.trace.node.IGeneratorNode; +import org.eclipse.xtext.generator.trace.node.IndentNode; +import org.eclipse.xtext.generator.trace.node.NewLineNode; +import org.eclipse.xtext.generator.trace.node.TemplateNode; +import org.eclipse.xtext.generator.trace.node.TextNode; +import org.eclipse.xtext.generator.trace.node.TraceNode; + +/** + * A builder API to create generator node trees + * + * @author Sven Efftinge - Initial contribution and API + */ +@SuppressWarnings("all") +public class GeneratorNodeExtensions { + @Inject + private GeneratorWhiteSpaceConfig wsConfig = new GeneratorWhiteSpaceConfig(); + + /** + * @return a root trace node for the given location + */ + public CompositeGeneratorNode trace(final ILocationData data) { + final TraceNode result = new TraceNode(data); + return result; + } + + /** + * @return a trace node for the given location, appended as a child on the given parent + */ + public CompositeGeneratorNode trace(final CompositeGeneratorNode parent, final ILocationData data) { + final TraceNode result = new TraceNode(data); + List _children = parent.getChildren(); + _children.add(result); + return result; + } + + /** + * @return an indentation node, using the default indentation string, appended as a child on the given parent + */ + public CompositeGeneratorNode indent(final CompositeGeneratorNode parent) { + return this.indent(parent, this.wsConfig.getIndentationString()); + } + + /** + * Appends the indentation string at the current position of the parent and adds a new composite node, indicating the same indentation for + * subsequent lines. + * + * @return an indentation node, using the given indentString, appended as a child on the given parent + */ + public CompositeGeneratorNode indent(final CompositeGeneratorNode parent, final String indentString) { + final TextNode text = new TextNode(indentString); + List _children = parent.getChildren(); + _children.add(text); + final IndentNode result = new IndentNode(indentString); + List _children_1 = parent.getChildren(); + _children_1.add(result); + return result; + } + + /** + * Appends a line separator node to the given parent. + * + * @return the given parent node + */ + public CompositeGeneratorNode appendNewLine(final CompositeGeneratorNode parent) { + List _children = parent.getChildren(); + String _lineDelimiter = this.wsConfig.getLineDelimiter(); + NewLineNode _newLineNode = new NewLineNode(_lineDelimiter, false); + _children.add(_newLineNode); + return parent; + } + + /** + * Appends a line separator node to the given parent. + * + * @return the given parent node + */ + public CompositeGeneratorNode appendNewLine(final CompositeGeneratorNode parent, final String lineSeparator) { + List _children = parent.getChildren(); + NewLineNode _newLineNode = new NewLineNode(lineSeparator, false); + _children.add(_newLineNode); + return parent; + } + + /** + * Appends a line separator node that will only be effective if the current line contains non-whitespace text. + * + * @return the given parent node + */ + public CompositeGeneratorNode appendNewLineIfNotEmpty(final CompositeGeneratorNode parent) { + List _children = parent.getChildren(); + String _lineDelimiter = this.wsConfig.getLineDelimiter(); + NewLineNode _newLineNode = new NewLineNode(_lineDelimiter, true); + _children.add(_newLineNode); + return parent; + } + + /** + * Creates a text node containing the toString() representation of the given object and + * appends it to the given parent node. + * + * @return the given parent node + */ + public CompositeGeneratorNode append(final CompositeGeneratorNode parent, final Object object) { + if ((object != null)) { + List _children = parent.getChildren(); + String _string = object.toString(); + TextNode _textNode = new TextNode(_string); + _children.add(_textNode); + } + return parent; + } + + /** + * Creates a template node for the given templateString and appends it to the given parent node. + * + * Templates are translated to generator node trees and expressions in templates can be of type IGeneratorNode. + * + * @return the given parent node + */ + public CompositeGeneratorNode appendTemplate(final CompositeGeneratorNode parent, final StringConcatenationClient templateString) { + final TemplateNode proc = new TemplateNode(templateString, this); + List _children = parent.getChildren(); + _children.add(proc); + return parent; + } +} diff --git a/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/GeneratorNodeProcessor.java b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/GeneratorNodeProcessor.java new file mode 100644 index 000000000..875eb03b2 --- /dev/null +++ b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/GeneratorNodeProcessor.java @@ -0,0 +1,381 @@ +/** + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.xtext.generator.trace.node; + +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Deque; +import java.util.List; +import java.util.stream.IntStream; +import org.eclipse.xtend.lib.annotations.Accessors; +import org.eclipse.xtend.lib.annotations.Data; +import org.eclipse.xtend.lib.annotations.Delegate; +import org.eclipse.xtext.generator.trace.AbstractStatefulTraceRegion; +import org.eclipse.xtext.generator.trace.AbstractTraceRegion; +import org.eclipse.xtext.generator.trace.ILocationData; +import org.eclipse.xtext.generator.trace.ITraceRegionProvider; +import org.eclipse.xtext.generator.trace.TraceNotFoundException; +import org.eclipse.xtext.generator.trace.node.CompositeGeneratorNode; +import org.eclipse.xtext.generator.trace.node.IGeneratorNode; +import org.eclipse.xtext.generator.trace.node.IndentNode; +import org.eclipse.xtext.generator.trace.node.NewLineNode; +import org.eclipse.xtext.generator.trace.node.TextNode; +import org.eclipse.xtext.generator.trace.node.TraceNode; +import org.eclipse.xtext.util.ITextRegion; +import org.eclipse.xtext.util.ITextRegionWithLineInformation; +import org.eclipse.xtext.util.TextRegionWithLineInformation; +import org.eclipse.xtext.xbase.lib.CollectionLiterals; +import org.eclipse.xtext.xbase.lib.Functions.Function2; +import org.eclipse.xtext.xbase.lib.IterableExtensions; +import org.eclipse.xtext.xbase.lib.ObjectExtensions; +import org.eclipse.xtext.xbase.lib.Procedures.Procedure1; +import org.eclipse.xtext.xbase.lib.Pure; + +/** + * @author Sven Efftinge - Initial contribution and API + */ +@SuppressWarnings("all") +public class GeneratorNodeProcessor { + @Data + public static class Result implements CharSequence, ITraceRegionProvider { + @Delegate + private final CharSequence contents; + + private final AbstractTraceRegion traceRegion; + + @Override + public AbstractTraceRegion getTraceRegion() throws TraceNotFoundException { + return this.traceRegion; + } + + @Override + public String toString() { + return this.contents.toString(); + } + + public Result(final CharSequence contents, final AbstractTraceRegion traceRegion) { + super(); + this.contents = contents; + this.traceRegion = traceRegion; + } + + @Override + @Pure + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.contents== null) ? 0 : this.contents.hashCode()); + result = prime * result + ((this.traceRegion== null) ? 0 : this.traceRegion.hashCode()); + return result; + } + + @Override + @Pure + public boolean equals(final Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + GeneratorNodeProcessor.Result other = (GeneratorNodeProcessor.Result) obj; + if (this.contents == null) { + if (other.contents != null) + return false; + } else if (!this.contents.equals(other.contents)) + return false; + if (this.traceRegion == null) { + if (other.traceRegion != null) + return false; + } else if (!this.traceRegion.equals(other.traceRegion)) + return false; + return true; + } + + @Pure + public CharSequence getContents() { + return this.contents; + } + + public char charAt(final int index) { + return this.contents.charAt(index); + } + + public IntStream chars() { + return this.contents.chars(); + } + + public IntStream codePoints() { + return this.contents.codePoints(); + } + + public int length() { + return this.contents.length(); + } + + public CharSequence subSequence(final int start, final int end) { + return this.contents.subSequence(start, end); + } + } + + @Accessors + protected static class Context { + private List lines; + + private Deque currentIndents; + + private boolean pendingIndent; + + private AbstractTraceRegion currentRegion = null; + + public StringBuilder currentLine() { + return this.lines.get(this.currentLineNumber()); + } + + public int contentLength() { + final Function2 _function = (Integer $0, StringBuilder $1) -> { + int _length = $1.length(); + return Integer.valueOf((($0).intValue() + _length)); + }; + final Integer contentLength = IterableExtensions.fold(this.lines, Integer.valueOf(0), _function); + if (this.pendingIndent) { + final Function2 _function_1 = (Integer $0, String $1) -> { + int _length = $1.length(); + return Integer.valueOf((($0).intValue() + _length)); + }; + Integer _fold = IterableExtensions.fold(this.currentIndents, Integer.valueOf(0), _function_1); + return ((contentLength).intValue() + (_fold).intValue()); + } else { + return (contentLength).intValue(); + } + } + + public int currentLineNumber() { + int _size = this.lines.size(); + return (_size - 1); + } + + @Pure + public List getLines() { + return this.lines; + } + + public void setLines(final List lines) { + this.lines = lines; + } + + @Pure + public Deque getCurrentIndents() { + return this.currentIndents; + } + + public void setCurrentIndents(final Deque currentIndents) { + this.currentIndents = currentIndents; + } + + @Pure + public boolean isPendingIndent() { + return this.pendingIndent; + } + + public void setPendingIndent(final boolean pendingIndent) { + this.pendingIndent = pendingIndent; + } + + @Pure + public AbstractTraceRegion getCurrentRegion() { + return this.currentRegion; + } + + public void setCurrentRegion(final AbstractTraceRegion currentRegion) { + this.currentRegion = currentRegion; + } + } + + /** + * Used to avoid multi-pass processing, when constructing a trace region tree. + * + * @author Sven Efftinge - Initial contribution and API + */ + public static class CompletableTraceRegion extends AbstractStatefulTraceRegion { + public static class CompletableTextRegion implements ITextRegionWithLineInformation { + private ITextRegionWithLineInformation delegate; + + @Delegate + public ITextRegionWithLineInformation getDelegate() { + if ((this.delegate == null)) { + throw new IllegalStateException("region not completed"); + } + return this.delegate; + } + + public int getEndLineNumber() { + return this.getDelegate().getEndLineNumber(); + } + + public int getLineNumber() { + return this.getDelegate().getLineNumber(); + } + + public ITextRegionWithLineInformation merge(final ITextRegionWithLineInformation other) { + return this.getDelegate().merge(other); + } + + public boolean contains(final ITextRegion other) { + return this.getDelegate().contains(other); + } + + public boolean contains(final int offset) { + return this.getDelegate().contains(offset); + } + + public int getLength() { + return this.getDelegate().getLength(); + } + + public int getOffset() { + return this.getDelegate().getOffset(); + } + + public ITextRegion merge(final ITextRegion region) { + return this.getDelegate().merge(region); + } + } + + private GeneratorNodeProcessor.CompletableTraceRegion.CompletableTextRegion region; + + public CompletableTraceRegion(final boolean useForDebugging, final ILocationData associatedLocation, final AbstractTraceRegion parent) { + this(new GeneratorNodeProcessor.CompletableTraceRegion.CompletableTextRegion(), useForDebugging, associatedLocation, parent); + } + + protected CompletableTraceRegion(final GeneratorNodeProcessor.CompletableTraceRegion.CompletableTextRegion region, final boolean useForDebugging, final ILocationData associatedLocation, final AbstractTraceRegion parent) { + super(region, useForDebugging, associatedLocation, parent); + this.region = region; + } + + public void complete(final int offset, final int length, final int startLine, final int endLine) { + TextRegionWithLineInformation _textRegionWithLineInformation = new TextRegionWithLineInformation(offset, length, startLine, endLine); + this.region.delegate = _textRegionWithLineInformation; + } + + @Override + protected boolean isConsistentWithParent() { + return true; + } + } + + public GeneratorNodeProcessor.Result process(final IGeneratorNode root) { + GeneratorNodeProcessor.Context _context = new GeneratorNodeProcessor.Context(); + final Procedure1 _function = (GeneratorNodeProcessor.Context it) -> { + StringBuilder _stringBuilder = new StringBuilder(); + it.lines = CollectionLiterals.newArrayList(_stringBuilder); + ArrayDeque _arrayDeque = new ArrayDeque(); + it.currentIndents = _arrayDeque; + it.pendingIndent = true; + }; + final GeneratorNodeProcessor.Context ctx = ObjectExtensions.operator_doubleArrow(_context, _function); + this.doProcess(root, ctx); + String _join = IterableExtensions.join(ctx.lines); + return new GeneratorNodeProcessor.Result(_join, ctx.currentRegion); + } + + /** + * Indent nodes apply indentation between newline and content of its children. + */ + protected void _doProcess(final IndentNode node, final GeneratorNodeProcessor.Context ctx) { + try { + ctx.currentIndents.push(node.getIndentationString()); + this.doProcessChildren(node, ctx); + } finally { + ctx.currentIndents.pop(); + } + } + + protected void _doProcess(final NewLineNode node, final GeneratorNodeProcessor.Context ctx) { + final String trimmedLine = ctx.currentLine().toString().trim(); + if ((node.isIfNotEmpty() && trimmedLine.isEmpty())) { + int _currentLineNumber = ctx.currentLineNumber(); + StringBuilder _stringBuilder = new StringBuilder(); + ctx.lines.set(_currentLineNumber, _stringBuilder); + return; + } + ctx.currentLine().append(node.getLineDelimiter()); + StringBuilder _stringBuilder_1 = new StringBuilder(); + ctx.lines.add(_stringBuilder_1); + ctx.pendingIndent = true; + } + + protected void _doProcess(final TextNode node, final GeneratorNodeProcessor.Context ctx) { + final String txt = node.getText().toString(); + boolean _isEmpty = txt.isEmpty(); + if (_isEmpty) { + return; + } + if (ctx.pendingIndent) { + final StringBuilder indentString = new StringBuilder(); + for (final String indentationString : ctx.currentIndents) { + indentString.append(indentationString); + } + ctx.currentLine().insert(0, indentString); + ctx.pendingIndent = false; + } + ctx.currentLine().append(node.getText()); + } + + protected void _doProcess(final CompositeGeneratorNode node, final GeneratorNodeProcessor.Context ctx) { + this.doProcessChildren(node, ctx); + } + + protected void _doProcess(final TraceNode node, final GeneratorNodeProcessor.Context ctx) { + final AbstractTraceRegion beforeRegion = ctx.currentRegion; + ILocationData _sourceLocation = node.getSourceLocation(); + final GeneratorNodeProcessor.CompletableTraceRegion newRegion = new GeneratorNodeProcessor.CompletableTraceRegion(false, _sourceLocation, beforeRegion); + final int offset = ctx.contentLength(); + final int startLineNumber = ctx.currentLineNumber(); + try { + ctx.currentRegion = newRegion; + this.doProcessChildren(node, ctx); + } finally { + if ((beforeRegion != null)) { + ctx.currentRegion = beforeRegion; + } + int _contentLength = ctx.contentLength(); + int _minus = (_contentLength - offset); + newRegion.complete(offset, _minus, startLineNumber, ctx.currentLineNumber()); + } + } + + protected void doProcessChildren(final CompositeGeneratorNode node, final GeneratorNodeProcessor.Context ctx) { + List _children = node.getChildren(); + for (final IGeneratorNode child : _children) { + this.doProcess(child, ctx); + } + } + + protected void doProcess(final IGeneratorNode node, final GeneratorNodeProcessor.Context ctx) { + if (node instanceof IndentNode) { + _doProcess((IndentNode)node, ctx); + return; + } else if (node instanceof TraceNode) { + _doProcess((TraceNode)node, ctx); + return; + } else if (node instanceof CompositeGeneratorNode) { + _doProcess((CompositeGeneratorNode)node, ctx); + return; + } else if (node instanceof NewLineNode) { + _doProcess((NewLineNode)node, ctx); + return; + } else if (node instanceof TextNode) { + _doProcess((TextNode)node, ctx); + return; + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays.asList(node, ctx).toString()); + } + } +} diff --git a/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/GeneratorWhiteSpaceConfig.java b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/GeneratorWhiteSpaceConfig.java new file mode 100644 index 000000000..fa4520dce --- /dev/null +++ b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/GeneratorWhiteSpaceConfig.java @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.xtext.generator.trace.node; + +/** + * @author Sven Efftinge - Initial contribution and API + */ +@SuppressWarnings("all") +public class GeneratorWhiteSpaceConfig { + public String getIndentationString() { + return " "; + } + + public String getLineDelimiter() { + return "\n"; + } +} diff --git a/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/IGeneratorNode.java b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/IGeneratorNode.java new file mode 100644 index 000000000..e256afc33 --- /dev/null +++ b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/IGeneratorNode.java @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.xtext.generator.trace.node; + +/** + * A node in the code generator graph + * + * @author Sven Efftinge - Initial contribution and API + */ +@SuppressWarnings("all") +public interface IGeneratorNode { +} diff --git a/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/IndentNode.java b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/IndentNode.java new file mode 100644 index 000000000..40c1b10d8 --- /dev/null +++ b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/IndentNode.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.xtext.generator.trace.node; + +import org.eclipse.xtend.lib.annotations.Accessors; +import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor; +import org.eclipse.xtext.generator.trace.node.CompositeGeneratorNode; +import org.eclipse.xtext.xbase.lib.Pure; + +/** + * @author Sven Efftinge - Initial contribution and API + */ +@FinalFieldsConstructor +@SuppressWarnings("all") +public class IndentNode extends CompositeGeneratorNode { + @Accessors + private final String indentationString; + + public IndentNode(final String indentationString) { + super(); + this.indentationString = indentationString; + } + + @Pure + public String getIndentationString() { + return this.indentationString; + } +} diff --git a/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/NewLineNode.java b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/NewLineNode.java new file mode 100644 index 000000000..7afc35327 --- /dev/null +++ b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/NewLineNode.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.xtext.generator.trace.node; + +import org.eclipse.xtend.lib.annotations.Data; +import org.eclipse.xtext.generator.trace.node.IGeneratorNode; +import org.eclipse.xtext.xbase.lib.Pure; +import org.eclipse.xtext.xbase.lib.util.ToStringBuilder; + +/** + * @author Sven Efftinge - Initial contribution and API + */ +@Data +@SuppressWarnings("all") +public class NewLineNode implements IGeneratorNode { + private final String lineDelimiter; + + private final boolean ifNotEmpty; + + public NewLineNode(final String lineDelimiter, final boolean ifNotEmpty) { + super(); + this.lineDelimiter = lineDelimiter; + this.ifNotEmpty = ifNotEmpty; + } + + @Override + @Pure + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.lineDelimiter== null) ? 0 : this.lineDelimiter.hashCode()); + result = prime * result + (this.ifNotEmpty ? 1231 : 1237); + return result; + } + + @Override + @Pure + public boolean equals(final Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + NewLineNode other = (NewLineNode) obj; + if (this.lineDelimiter == null) { + if (other.lineDelimiter != null) + return false; + } else if (!this.lineDelimiter.equals(other.lineDelimiter)) + return false; + if (other.ifNotEmpty != this.ifNotEmpty) + return false; + return true; + } + + @Override + @Pure + public String toString() { + ToStringBuilder b = new ToStringBuilder(this); + b.add("lineDelimiter", this.lineDelimiter); + b.add("ifNotEmpty", this.ifNotEmpty); + return b.toString(); + } + + @Pure + public String getLineDelimiter() { + return this.lineDelimiter; + } + + @Pure + public boolean isIfNotEmpty() { + return this.ifNotEmpty; + } +} diff --git a/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/TemplateNode.java b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/TemplateNode.java new file mode 100644 index 000000000..456bdc913 --- /dev/null +++ b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/TemplateNode.java @@ -0,0 +1,163 @@ +/** + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.xtext.generator.trace.node; + +import com.google.common.base.Splitter; +import com.google.common.collect.Iterables; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Pattern; +import org.eclipse.xtend2.lib.StringConcatenationClient; +import org.eclipse.xtext.generator.trace.node.CompositeGeneratorNode; +import org.eclipse.xtext.generator.trace.node.GeneratorNodeExtensions; +import org.eclipse.xtext.generator.trace.node.IGeneratorNode; +import org.eclipse.xtext.generator.trace.node.IndentNode; +import org.eclipse.xtext.generator.trace.node.TextNode; +import org.eclipse.xtext.xbase.lib.CollectionLiterals; +import org.eclipse.xtext.xbase.lib.Functions.Function1; +import org.eclipse.xtext.xbase.lib.Functions.Function2; +import org.eclipse.xtext.xbase.lib.IterableExtensions; +import org.eclipse.xtext.xbase.lib.ListExtensions; + +/** + * @author Sven Efftinge - Initial contribution and API + */ +@SuppressWarnings("all") +public class TemplateNode extends CompositeGeneratorNode implements StringConcatenationClient.TargetStringConcatenation { + private final GeneratorNodeExtensions nodeFactory; + + public TemplateNode(final StringConcatenationClient contents, final GeneratorNodeExtensions nodeFactory) { + this.nodeFactory = nodeFactory; + StringConcatenationClient.appendTo(contents, this); + } + + private CompositeGeneratorNode currentParent = this; + + private boolean isEmptyLine = true; + + @Override + public void append(final Object object, final String indentation) { + int _length = indentation.length(); + boolean _greaterThan = (_length > 0); + if (_greaterThan) { + final CompositeGeneratorNode before = this.currentParent; + try { + IndentNode _indentNode = new IndentNode(indentation); + this.currentParent = _indentNode; + List _children = before.getChildren(); + _children.add(this.currentParent); + this.append(object); + } finally { + this.currentParent = before; + } + } else { + this.append(object); + } + } + + private final Splitter lineSplitter = Splitter.on(Pattern.compile("\\R")); + + @Override + public void append(final Object object) { + boolean _matched = false; + if (object instanceof StringConcatenationClient) { + _matched=true; + this.nodeFactory.appendTemplate(this.currentParent, ((StringConcatenationClient)object)); + } + if (!_matched) { + if (object instanceof IGeneratorNode) { + _matched=true; + List _children = this.currentParent.getChildren(); + _children.add(((IGeneratorNode)object)); + } + } + if (!_matched) { + { + final String str = object.toString(); + final Iterator iter = this.lineSplitter.split(str).iterator(); + while (iter.hasNext()) { + { + final String segment = iter.next(); + boolean _isEmpty = segment.isEmpty(); + boolean _not = (!_isEmpty); + if (_not) { + this.isEmptyLine = false; + } + this.nodeFactory.append(this.currentParent, segment); + boolean _hasNext = iter.hasNext(); + if (_hasNext) { + this.newLine(); + } + } + } + } + } + } + + protected Iterable leafsBackwards(final IGeneratorNode it) { + Iterable _switchResult = null; + boolean _matched = false; + if (it instanceof CompositeGeneratorNode) { + _matched=true; + final Function1> _function = (IGeneratorNode it_1) -> { + return this.leafsBackwards(it_1); + }; + final Function2, Iterable, Iterable> _function_1 = (Iterable p1, Iterable p2) -> { + return Iterables.concat(p1, p2); + }; + _switchResult = IterableExtensions.>reduce(ListExtensions.>map(ListExtensions.reverseView(((CompositeGeneratorNode)it).getChildren()), _function), _function_1); + } + if (!_matched) { + _switchResult = Collections.unmodifiableList(CollectionLiterals.newArrayList(it)); + } + return _switchResult; + } + + @Override + public void appendImmediate(final Object object, final String indentation) { + for (int i = this.currentParent.getChildren().size(); (i >= 0); i--) { + { + final IGeneratorNode node = this.currentParent.getChildren().get(i); + if ((node instanceof TextNode)) { + int _length = ((TextNode)node).getText().toString().trim().length(); + boolean _tripleEquals = (_length == 0); + if (_tripleEquals) { + this.currentParent.getChildren().remove(i); + } + } + } + } + this.append(object, indentation); + } + + @Override + public void newLine() { + this.nodeFactory.appendNewLine(this.currentParent); + } + + @Override + public void newLineIfNotEmpty() { + this.nodeFactory.appendNewLineIfNotEmpty(this.currentParent); + } + + @Override + public char charAt(final int index) { + throw new UnsupportedOperationException("TODO: auto-generated method stub"); + } + + @Override + public int length() { + throw new UnsupportedOperationException("TODO: auto-generated method stub"); + } + + @Override + public CharSequence subSequence(final int start, final int end) { + throw new UnsupportedOperationException("TODO: auto-generated method stub"); + } +} diff --git a/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/TextNode.java b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/TextNode.java new file mode 100644 index 000000000..322dc7643 --- /dev/null +++ b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/TextNode.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.xtext.generator.trace.node; + +import org.eclipse.xtend.lib.annotations.Data; +import org.eclipse.xtext.generator.trace.node.IGeneratorNode; +import org.eclipse.xtext.xbase.lib.Pure; +import org.eclipse.xtext.xbase.lib.util.ToStringBuilder; + +/** + * @author Sven Efftinge - Initial contribution and API + */ +@Data +@SuppressWarnings("all") +public class TextNode implements IGeneratorNode { + private final CharSequence text; + + public TextNode(final CharSequence text) { + super(); + this.text = text; + } + + @Override + @Pure + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.text== null) ? 0 : this.text.hashCode()); + return result; + } + + @Override + @Pure + public boolean equals(final Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TextNode other = (TextNode) obj; + if (this.text == null) { + if (other.text != null) + return false; + } else if (!this.text.equals(other.text)) + return false; + return true; + } + + @Override + @Pure + public String toString() { + ToStringBuilder b = new ToStringBuilder(this); + b.add("text", this.text); + return b.toString(); + } + + @Pure + public CharSequence getText() { + return this.text; + } +} diff --git a/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/TraceNode.java b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/TraceNode.java new file mode 100644 index 000000000..fd364de74 --- /dev/null +++ b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/TraceNode.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2017 TypeFox (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.xtext.generator.trace.node; + +import org.eclipse.xtend.lib.annotations.Accessors; +import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor; +import org.eclipse.xtext.generator.trace.ILocationData; +import org.eclipse.xtext.generator.trace.node.CompositeGeneratorNode; +import org.eclipse.xtext.xbase.lib.Pure; + +/** + * @author Sven Efftinge - initial contribution and API + */ +@FinalFieldsConstructor +@SuppressWarnings("all") +public class TraceNode extends CompositeGeneratorNode { + @Accessors + private final ILocationData sourceLocation; + + public TraceNode(final ILocationData sourceLocation) { + super(); + this.sourceLocation = sourceLocation; + } + + @Pure + public ILocationData getSourceLocation() { + return this.sourceLocation; + } +} diff --git a/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/Traced.java b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/Traced.java new file mode 100644 index 000000000..1aade423a --- /dev/null +++ b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/Traced.java @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.xtext.generator.trace.node; + +import org.eclipse.xtend.lib.macro.Active; +import org.eclipse.xtext.generator.trace.node.TracedProcessor; + +/** + * @author Sven Efftinge - Initial contribution and API + */ +@Active(TracedProcessor.class) +public @interface Traced { + public String tracingSugarFieldName() default "_traceExtensions"; +} diff --git a/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/TracedAccessors.java b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/TracedAccessors.java new file mode 100644 index 000000000..3ccfbb603 --- /dev/null +++ b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/TracedAccessors.java @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.xtext.generator.trace.node; + +import org.eclipse.emf.ecore.EFactory; +import org.eclipse.xtend.lib.macro.Active; +import org.eclipse.xtext.generator.trace.node.TracedAccessorsProcessor; + +/** + * @author Sven Efftinge - Initial contribution and API + */ +@Active(TracedAccessorsProcessor.class) +public @interface TracedAccessors { + public Class[] value(); +} diff --git a/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/TracedAccessorsProcessor.java b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/TracedAccessorsProcessor.java new file mode 100644 index 000000000..c46ac2b62 --- /dev/null +++ b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/TracedAccessorsProcessor.java @@ -0,0 +1,189 @@ +/** + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.xtext.generator.trace.node; + +import com.google.common.collect.Iterables; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.xtend.lib.macro.AbstractClassProcessor; +import org.eclipse.xtend.lib.macro.TransformationContext; +import org.eclipse.xtend.lib.macro.declaration.AnnotationReference; +import org.eclipse.xtend.lib.macro.declaration.InterfaceDeclaration; +import org.eclipse.xtend.lib.macro.declaration.MethodDeclaration; +import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration; +import org.eclipse.xtend.lib.macro.declaration.MutableMethodDeclaration; +import org.eclipse.xtend.lib.macro.declaration.ResolvedMethod; +import org.eclipse.xtend.lib.macro.declaration.Type; +import org.eclipse.xtend.lib.macro.declaration.TypeReference; +import org.eclipse.xtend2.lib.StringConcatenationClient; +import org.eclipse.xtext.generator.trace.ILocationData; +import org.eclipse.xtext.generator.trace.node.CompositeGeneratorNode; +import org.eclipse.xtext.generator.trace.node.IGeneratorNode; +import org.eclipse.xtext.generator.trace.node.TracedAccessors; +import org.eclipse.xtext.generator.trace.node.TracingSugar; +import org.eclipse.xtext.xbase.lib.CollectionLiterals; +import org.eclipse.xtext.xbase.lib.Conversions; +import org.eclipse.xtext.xbase.lib.Extension; +import org.eclipse.xtext.xbase.lib.Functions.Function1; +import org.eclipse.xtext.xbase.lib.IterableExtensions; +import org.eclipse.xtext.xbase.lib.ListExtensions; +import org.eclipse.xtext.xbase.lib.Procedures.Procedure1; +import org.eclipse.xtext.xbase.lib.StringExtensions; + +@SuppressWarnings("all") +public class TracedAccessorsProcessor extends AbstractClassProcessor { + @Override + public void doTransform(final MutableClassDeclaration annotatedClass, @Extension final TransformationContext context) { + annotatedClass.setExtendedClass(context.newTypeReference(TracingSugar.class)); + final TypeReference iterableType = context.newTypeReference(Iterable.class, context.newWildcardTypeReference()); + final TypeReference annotationType = context.newTypeReference(TracedAccessors.class); + AnnotationReference _findAnnotation = annotatedClass.findAnnotation(annotationType.getType()); + TypeReference[] _classArrayValue = null; + if (_findAnnotation!=null) { + _classArrayValue=_findAnnotation.getClassArrayValue("value"); + } + final TypeReference[] factories = _classArrayValue; + if ((factories == null)) { + return; + } + final Function1 _function = (TypeReference it) -> { + return it.getType(); + }; + Iterable _filter = Iterables.filter(ListExtensions.map(((List)Conversions.doWrapArray(factories)), _function), InterfaceDeclaration.class); + for (final InterfaceDeclaration f : _filter) { + final Function1 _function_1 = (MethodDeclaration it) -> { + return Boolean.valueOf((it.getSimpleName().startsWith("create") && IterableExtensions.isEmpty(it.getParameters()))); + }; + final Function1 _function_2 = (MethodDeclaration it) -> { + return it.getReturnType(); + }; + Iterable _map = IterableExtensions.map(IterableExtensions.filter(f.getDeclaredMethods(), _function_1), _function_2); + for (final TypeReference t : _map) { + final Function1 _function_3 = (ResolvedMethod it) -> { + return Boolean.valueOf(this.isSupportedGetter(it)); + }; + final Function1 _function_4 = (ResolvedMethod it) -> { + boolean _isAssignableFrom = iterableType.isAssignableFrom(it.getDeclaration().getReturnType()); + return Boolean.valueOf((!_isAssignableFrom)); + }; + Iterable _filter_1 = IterableExtensions.filter(IterableExtensions.filter(t.getAllResolvedMethods(), _function_3), _function_4); + for (final ResolvedMethod getter : _filter_1) { + { + final TypeReference rt = getter.getResolvedReturnType(); + boolean _contains = TracedAccessorsProcessor.TYPES_WITH_GOOD_TO_STRING.contains(rt.getType().getSimpleName().toLowerCase()); + if (_contains) { + final Procedure1 _function_5 = (MutableMethodDeclaration it) -> { + it.setReturnType(context.newTypeReference(IGeneratorNode.class)); + it.addParameter("target", t); + StringConcatenationClient _client = new StringConcatenationClient() { + @Override + protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) { + _builder.append(EStructuralFeature.class); + _builder.append(" feature = target.eClass().getEStructuralFeature(\""); + String _featureName = TracedAccessorsProcessor.this.featureName(getter); + _builder.append(_featureName); + _builder.append("\");"); + _builder.newLineIfNotEmpty(); + _builder.append(ILocationData.class); + _builder.append(" location = this.location(target, feature, -1);"); + _builder.newLineIfNotEmpty(); + _builder.append(CompositeGeneratorNode.class); + _builder.append(" trace = this.trace(location);"); + _builder.newLineIfNotEmpty(); + _builder.append("this.append(trace, target."); + String _simpleName = getter.getDeclaration().getSimpleName(); + _builder.append(_simpleName); + _builder.append("());"); + _builder.newLineIfNotEmpty(); + _builder.append("return trace;"); + _builder.newLine(); + } + }; + it.setBody(_client); + }; + annotatedClass.addMethod(this.tracerName(getter), _function_5); + } + final Procedure1 _function_6 = (MutableMethodDeclaration it) -> { + it.setReturnType(context.newTypeReference(IGeneratorNode.class)); + it.addParameter("target", t); + final TypeReference stringProvider = context.newTypeReference(Function.class, rt, context.getString()); + it.addParameter("stringProvider", stringProvider); + StringConcatenationClient _client = new StringConcatenationClient() { + @Override + protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) { + _builder.append(EStructuralFeature.class); + _builder.append(" feature = target.eClass().getEStructuralFeature(\""); + String _featureName = TracedAccessorsProcessor.this.featureName(getter); + _builder.append(_featureName); + _builder.append("\");"); + _builder.newLineIfNotEmpty(); + _builder.append(ILocationData.class); + _builder.append(" location = this.location(target, feature, -1);"); + _builder.newLineIfNotEmpty(); + _builder.append(CompositeGeneratorNode.class); + _builder.append(" trace = this.trace(location);"); + _builder.newLineIfNotEmpty(); + _builder.append("this.append(trace, stringProvider.apply(target."); + String _simpleName = getter.getDeclaration().getSimpleName(); + _builder.append(_simpleName); + _builder.append("()));"); + _builder.newLineIfNotEmpty(); + _builder.append("return trace;"); + _builder.newLine(); + } + }; + it.setBody(_client); + }; + annotatedClass.addMethod(this.tracerName(getter), _function_6); + } + } + } + } + } + + public String tracerName(final ResolvedMethod m) { + String _featureName = this.featureName(m); + return ("_" + _featureName); + } + + public String featureName(final ResolvedMethod m) { + String _xblockexpression = null; + { + final String n = m.getDeclaration().getSimpleName(); + int _xifexpression = (int) 0; + boolean _startsWith = n.startsWith("get"); + if (_startsWith) { + _xifexpression = 3; + } else { + _xifexpression = 2; + } + final int skip = _xifexpression; + _xblockexpression = StringExtensions.toFirstLower(m.getDeclaration().getSimpleName().substring(skip)); + } + return _xblockexpression; + } + + private final static Set TYPES_WITH_GOOD_TO_STRING = Collections.unmodifiableSet(CollectionLiterals.newHashSet("string", "boolean", "int", "long", "integer")); + + public boolean isSupportedGetter(final ResolvedMethod it) { + boolean _isEmpty = IterableExtensions.isEmpty(it.getDeclaration().getParameters()); + boolean _not = (!_isEmpty); + if (_not) { + return false; + } + boolean _isStatic = it.getDeclaration().isStatic(); + if (_isStatic) { + return false; + } + final String n = it.getDeclaration().getSimpleName(); + return (n.startsWith("get") || n.startsWith("is")); + } +} diff --git a/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/TracedProcessor.java b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/TracedProcessor.java new file mode 100644 index 000000000..88a335422 --- /dev/null +++ b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/TracedProcessor.java @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.xtext.generator.trace.node; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtend.lib.macro.AbstractMethodProcessor; +import org.eclipse.xtend.lib.macro.TransformationContext; +import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration; +import org.eclipse.xtend.lib.macro.declaration.MutableFieldDeclaration; +import org.eclipse.xtend.lib.macro.declaration.MutableMethodDeclaration; +import org.eclipse.xtend.lib.macro.declaration.MutableParameterDeclaration; +import org.eclipse.xtend.lib.macro.declaration.MutableTypeDeclaration; +import org.eclipse.xtend.lib.macro.declaration.TypeReference; +import org.eclipse.xtend2.lib.StringConcatenation; +import org.eclipse.xtend2.lib.StringConcatenationClient; +import org.eclipse.xtext.generator.trace.ILocationData; +import org.eclipse.xtext.generator.trace.node.CompositeGeneratorNode; +import org.eclipse.xtext.generator.trace.node.IGeneratorNode; +import org.eclipse.xtext.generator.trace.node.Traced; +import org.eclipse.xtext.generator.trace.node.TracingSugar; +import org.eclipse.xtext.xbase.lib.Extension; +import org.eclipse.xtext.xbase.lib.Functions.Function1; +import org.eclipse.xtext.xbase.lib.IterableExtensions; +import org.eclipse.xtext.xbase.lib.Procedures.Procedure1; + +@SuppressWarnings("all") +public class TracedProcessor extends AbstractMethodProcessor { + @Override + public void doTransform(final MutableMethodDeclaration annotatedMethod, @Extension final TransformationContext context) { + final TypeReference traceSugar = context.newTypeReference(TracingSugar.class); + final TypeReference templateClient = context.newTypeReference(StringConcatenationClient.class); + final TypeReference nodeType = context.newTypeReference(IGeneratorNode.class); + final TypeReference eobjectType = context.newTypeReference(EObject.class); + MutableTypeDeclaration _declaringType = annotatedMethod.getDeclaringType(); + final MutableClassDeclaration clazz = ((MutableClassDeclaration) _declaringType); + final Function1 _function = (MutableFieldDeclaration it) -> { + return Boolean.valueOf(traceSugar.isAssignableFrom(it.getType())); + }; + final MutableFieldDeclaration field = IterableExtensions.findFirst(clazz.getDeclaredFields(), _function); + if ((field == null)) { + StringConcatenation _builder = new StringConcatenation(); + _builder.append("@"); + String _simpleName = Traced.class.getSimpleName(); + _builder.append(_simpleName); + _builder.append(" methods can only declared in a class with a field of type "); + _builder.append(TracingSugar.class); + context.addError(annotatedMethod, _builder.toString()); + return; + } + final Function1 _function_1 = (MutableParameterDeclaration it) -> { + return Boolean.valueOf(eobjectType.isAssignableFrom(it.getType())); + }; + final MutableParameterDeclaration traceParam = IterableExtensions.findFirst(annotatedMethod.getParameters(), _function_1); + if ((traceParam == null)) { + StringConcatenation _builder_1 = new StringConcatenation(); + _builder_1.append("@"); + String _simpleName_1 = Traced.class.getSimpleName(); + _builder_1.append(_simpleName_1); + _builder_1.append(" methods need at least one parameter of type "); + _builder_1.append(EObject.class); + _builder_1.append("."); + context.addError(annotatedMethod, _builder_1.toString()); + return; + } + String _simpleName_2 = annotatedMethod.getSimpleName(); + String _plus = ("_" + _simpleName_2); + final Procedure1 _function_2 = (MutableMethodDeclaration it) -> { + Iterable _parameters = annotatedMethod.getParameters(); + for (final MutableParameterDeclaration p : _parameters) { + it.addParameter(p.getSimpleName(), p.getType()); + } + it.setReturnType(templateClient); + it.setBody(annotatedMethod.getBody()); + }; + clazz.addMethod(_plus, _function_2); + annotatedMethod.setReturnType(nodeType); + StringConcatenationClient _client = new StringConcatenationClient() { + @Override + protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) { + _builder.append(ILocationData.class); + _builder.append(" _location = this."); + String _simpleName = field.getSimpleName(); + _builder.append(_simpleName); + _builder.append(".location("); + String _simpleName_1 = traceParam.getSimpleName(); + _builder.append(_simpleName_1); + _builder.append(");"); + _builder.newLineIfNotEmpty(); + _builder.append(CompositeGeneratorNode.class); + _builder.append(" _traceNode = this."); + String _simpleName_2 = field.getSimpleName(); + _builder.append(_simpleName_2); + _builder.append(".trace(_location);"); + _builder.newLineIfNotEmpty(); + _builder.append("this."); + String _simpleName_3 = field.getSimpleName(); + _builder.append(_simpleName_3); + _builder.append(".appendTemplate(_traceNode, _"); + String _simpleName_4 = annotatedMethod.getSimpleName(); + _builder.append(_simpleName_4); + _builder.append("("); + final Function1 _function = (MutableParameterDeclaration it) -> { + return it.getSimpleName(); + }; + String _join = IterableExtensions.join(annotatedMethod.getParameters(), ",", _function); + _builder.append(_join); + _builder.append("));"); + _builder.newLineIfNotEmpty(); + _builder.append("return _traceNode;"); + _builder.newLine(); + } + }; + annotatedMethod.setBody(_client); + } +} diff --git a/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/TracingSugar.java b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/TracingSugar.java new file mode 100644 index 000000000..035035037 --- /dev/null +++ b/org.eclipse.xtext/xtend-gen/org/eclipse/xtext/generator/trace/node/TracingSugar.java @@ -0,0 +1,91 @@ +package org.eclipse.xtext.generator.trace.node; + +import com.google.inject.Inject; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.xtend2.lib.StringConcatenationClient; +import org.eclipse.xtext.generator.IFileSystemAccess2; +import org.eclipse.xtext.generator.trace.ILocationData; +import org.eclipse.xtext.generator.trace.ITraceURIConverter; +import org.eclipse.xtext.generator.trace.LocationData; +import org.eclipse.xtext.generator.trace.SourceRelativeURI; +import org.eclipse.xtext.generator.trace.node.CompositeGeneratorNode; +import org.eclipse.xtext.generator.trace.node.GeneratorNodeExtensions; +import org.eclipse.xtext.generator.trace.node.GeneratorNodeProcessor; +import org.eclipse.xtext.generator.trace.node.GeneratorWhiteSpaceConfig; +import org.eclipse.xtext.resource.ILocationInFileProvider; +import org.eclipse.xtext.util.ITextRegion; +import org.eclipse.xtext.util.ITextRegionWithLineInformation; + +/** + * Some additional sugar extension to + * - create generator nodes for EObjects + * - create ILocationData for EObjects. + * - enhance FileSystemAccess for tracing + */ +@SuppressWarnings("all") +public class TracingSugar extends GeneratorNodeExtensions { + @Inject + protected ILocationInFileProvider locationProvider; + + @Inject + protected ITraceURIConverter traceURIConverter; + + @Inject + protected GeneratorWhiteSpaceConfig whiteSpaceConfig; + + @Inject + protected GeneratorNodeProcessor processor; + + /** + * Convenience extension, to generate traced code. + */ + public void generateTracedFile(final IFileSystemAccess2 fsa, final String path, final EObject rootTrace, final StringConcatenationClient code) { + final CompositeGeneratorNode node = this.trace(rootTrace, code); + this.generateTracedFile(fsa, path, node); + } + + /** + * Use to generate a file based on generator node. + */ + public void generateTracedFile(final IFileSystemAccess2 fsa, final String path, final CompositeGeneratorNode rootNode) { + final GeneratorNodeProcessor.Result result = this.processor.process(rootNode); + fsa.generateFile(path, result); + } + + /** + * Convenience shorthand for obj.location.trace + */ + public CompositeGeneratorNode trace(final EObject obj) { + return this.trace(this.location(obj)); + } + + /** + * Convenience shorthand for obj.trace.appendTemplate('''some template''') + */ + public CompositeGeneratorNode trace(final EObject obj, final StringConcatenationClient code) { + return this.appendTemplate(this.trace(obj), code); + } + + /** + * @return ILocationData covering the fullTextRegion of the given EObject. + */ + public ILocationData location(final EObject obj) { + final ITextRegion region = this.locationProvider.getFullTextRegion(obj); + final SourceRelativeURI uri = this.traceURIConverter.getURIForTrace(obj.eResource()); + return new LocationData(((ITextRegionWithLineInformation) region), uri); + } + + /** + * @param obj the EObject containing the feature + * @param feature the EStructuralFeature to trace + * @param idx the index of the value to trace, in case the feature contains a list, should be -1 otherwise. + * + * @return ILocationData covering the fullTextRegion of the given feature in the given EObject. + */ + public ILocationData location(final EObject obj, final EStructuralFeature feature, final int idx) { + final ITextRegion region = this.locationProvider.getFullTextRegion(obj, feature, idx); + final SourceRelativeURI uri = this.traceURIConverter.getURIForTrace(obj.eResource()); + return new LocationData(((ITextRegionWithLineInformation) region), uri); + } +}