Repository: SpoonLabs/gumtree-spoon-ast-diff (java)

Issue:
Offsets are all zeros [0,0]
Hello, I am just getting started with Spoon.

I am trying to add GT-Spoon as one of the tools for our [DiffBenchmark](https://github.com/pouryafard75/DiffBenchmark). However, I stumbled upon a problem which is having zeros as all the offsets. Our benchmark is originally built for gumtree-jdt visitor but we want to find the eqv subtree for other visitors by relying on the offsets.

I have tried the ASTComperator with the [annotation example](https://github.com/SpoonLabs/gumtree-spoon-ast-diff/tree/master/src/test/resources/examples/annotations), and tried to print the generated trees using the following code:
```
MappingStore mappingsComp = result.getMappingsComp();
		System.out.println(mappingsComp.src.toTreeString());
		System.out.println(mappingsComp.dst.toTreeString());
```
However the offsets are all shown as zero:
```
root [0,0]
    RootPac: unnamed package [0,0]
        Class: Test [0,0]
            Method: hashCode [0,0]
                RETURN_TYPE: int [0,0]
                Modifiers_Method [0,0]
                    Modifier: public [0,0]
                Annotation: java.lang.Override [0,0]
                Return: return [0,0]
                    UnaryOperator: NEG [0,0]
                        Literal: 1 [0,0]
root [0,0]
    RootPac: unnamed package [0,0]
        Class: Test [0,0]
            Method: hashCode [0,0]
                RETURN_TYPE: int [0,0]
                Modifiers_Method [0,0]
                    Modifier: public [0,0]
                Annotation: java.lang.Deprecated [0,0]
                Return: return [0,0]
                    UnaryOperator: NEG [0,0]
                        Literal: 1 [0,0]
```


Pull request description:
feat: record positions of each node in Tree
Fixes #323 

We record start and end position characters in each node in the Gumtree tree. Thankfully, Spoon nodes provide this information in these [two APIs](https://github.com/INRIA/spoon/blob/c60af45512223894e32a5fd2963079a88d22c388/src/main/java/spoon/reflect/cu/SourcePosition.java#L74-L82).

Most of the nodes in Spoon are visited in `TreeScanner` and they are added to Gumtree AST. However, there are many more nodes created in `NodeCreator` for which I have added the position manually. Hence, they require testing. This is why I am marking this PR as draft right now. I will add some tests soon and then merge. @pouryafard75, please try out this PR and report any bugs in the positions you get. It will also help me write tests. :)

Patch:
diff --git a/pom.xml b/pom.xml
index 27e4142..7b80170 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,7 +26,7 @@
     <java.test.version>11</java.test.version>
 
     <!-- Dependencies -->
-    <cli.version>1.8.0</cli.version>
+    <cli.version>1.9.0</cli.version>
 
     <!-- Test dependencies -->
     <junit.version>4.13.2</junit.version>
@@ -36,7 +36,7 @@
     <dependency>
       <groupId>fr.inria.gforge.spoon</groupId>
       <artifactId>spoon-core</artifactId>
-      <version>11.0.0</version>
+      <version>11.1.0</version>
     </dependency>
     <dependency>
       <groupId>com.github.gumtreediff</groupId>
@@ -132,7 +132,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-surefire-plugin</artifactId>
-        <version>3.2.5</version>
+        <version>3.5.0</version>
         <configuration>
           <!-- the following is required to have the same execution semantics as eclipse (hence all tests passing) -->
           <!-- the reason is that it switches Spoon in noclasspath or not for this particular file -->
@@ -144,7 +144,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-release-plugin</artifactId>
-        <version>3.0.1</version>
+        <version>3.1.1</version>
         <configuration>
           <autoVersionSubmodules>true</autoVersionSubmodules>
           <useReleaseProfile>false</useReleaseProfile>
@@ -156,7 +156,7 @@
       <plugin>
         <groupId>org.sonatype.plugins</groupId>
         <artifactId>nexus-staging-maven-plugin</artifactId>
-        <version>1.6.13</version>
+        <version>1.7.0</version>
         <extensions>true</extensions>
         <configuration>
           <serverId>ossrh</serverId>
@@ -189,7 +189,7 @@
           <plugin>
             <groupId>org.apache.maven.plugins</groupId>
             <artifactId>maven-javadoc-plugin</artifactId>
-            <version>3.6.3</version>
+            <version>3.10.0</version>
             <configuration>
                     <doclint>none</doclint>
                     <detectJavaApiLink>false</detectJavaApiLink>
@@ -207,7 +207,7 @@
           <plugin>
             <groupId>org.apache.maven.plugins</groupId>
             <artifactId>maven-gpg-plugin</artifactId>
-            <version>3.2.0</version>
+            <version>3.2.6</version>
             <executions>
               <execution>
                 <id>sign-artifacts</id>
diff --git a/renovate.json b/renovate.json
index 39a2b6e..afdfacc 100644
--- a/renovate.json
+++ b/renovate.json
@@ -2,5 +2,6 @@
   "$schema": "https://docs.renovatebot.com/renovate-schema.json",
   "extends": [
     "config:base"
-  ]
+  ],
+  "automerge": true
 }
diff --git a/src/main/java/gumtree/spoon/builder/NodeCreator.java b/src/main/java/gumtree/spoon/builder/NodeCreator.java
index 0faa022..b0b1027 100644
--- a/src/main/java/gumtree/spoon/builder/NodeCreator.java
+++ b/src/main/java/gumtree/spoon/builder/NodeCreator.java
@@ -17,16 +17,7 @@
 
 package gumtree.spoon.builder;
 
-import java.lang.annotation.Annotation;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
-
 import com.github.gumtreediff.tree.Tree;
-
 import spoon.reflect.code.CtExpression;
 import spoon.reflect.cu.CompilationUnit;
 import spoon.reflect.cu.SourcePosition;
@@ -45,199 +36,252 @@ import spoon.reflect.visitor.CtInheritanceScanner;
 import spoon.support.reflect.CtExtendedModifier;
 import spoon.support.reflect.cu.position.SourcePositionImpl;
 
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
 /**
  * responsible to add additional nodes only overrides scan* to add new nodes
  */
 public class NodeCreator extends CtInheritanceScanner {
-	public static final String MODIFIERS = "Modifiers_";
-	private final TreeScanner builder;
-
-	NodeCreator(TreeScanner builder) {
-		this.builder = builder;
-	}
-
-	@Override
-	public void scanCtModifiable(CtModifiable m) {
-
-		if (m.getModifiers().isEmpty())
-			return;
-
-		// We add the type of modifiable element
-		String type = MODIFIERS + getClassName(m.getClass().getSimpleName());
-		Tree modifiers = builder.createNode(type, "");
-
-		// ensuring an order (instead of hashset)
-		// otherwise some flaky tests in CI
-		Set<CtExtendedModifier> modifiers1 = new TreeSet<>(Comparator.comparing(o -> o.getKind().name()));
-		modifiers1.addAll(m.getExtendedModifiers());
-
-		// We create a virtual node
-		CtVirtualElement virtualElement = new CtVirtualElement(type, m, m.getModifiers(), CtRole.MODIFIER);
-		modifiers.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, virtualElement);
-
-		List<SourcePosition> modifierPositions = new ArrayList<>();
-
-		for (CtExtendedModifier mod : modifiers1) {
-			Tree modifier = builder.createNode("Modifier", mod.getKind().toString());
-			modifiers.addChild(modifier);
-			// We wrap the modifier's kind (which is not a CtElement)
-			CtWrapper wrapper = new CtWrapper<>(mod.getKind(), m, CtRole.MODIFIER);
-			wrapper.setPosition(mod.getPosition());
-			modifierPositions.add(mod.getPosition());
-			modifier.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, wrapper);
-		}
-
-		virtualElement.setPosition(computeSourcePositionOfVirtualElement(modifierPositions));
-		builder.addSiblingNode(modifiers);
-	}
-
-	private static SourcePosition computeSourcePositionOfVirtualElement(List<SourcePosition> modifierPositions) {
-		CompilationUnit cu = null;
-		Integer sourceStart = null;
-		Integer sourceEnd = null;
-		for (SourcePosition position : modifierPositions) {
-			if (position instanceof NoSourcePosition) { return SourcePosition.NOPOSITION; }
-			if (sourceStart == null || position.getSourceStart() < sourceStart) {
-				sourceStart = position.getSourceStart();
-			}
-			if (sourceEnd == null || position.getSourceEnd() > sourceEnd) {
-				sourceEnd = position.getSourceEnd();
-			}
-			if (cu == null) { cu = position.getCompilationUnit(); }
-		}
-		return new SourcePositionImpl(cu, sourceStart, sourceEnd, cu.getLineSeparatorPositions());
-	}
-
-	private String getClassName(String simpleName) {
-		if (simpleName == null)
-			return "";
-		return simpleName.replace("Ct", "").replace("Impl", "");
-	}
-
-	@Override
-	public <T> void scanCtVariable(CtVariable<T> e) {
-		CtTypeReference<T> type = e.getType();
-		if (type != null) {
-			Tree variableType = builder.createNode("VARIABLE_TYPE", type.getQualifiedName());
-			variableType.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, type);
-			type.putMetadata(SpoonGumTreeBuilder.GUMTREE_NODE, variableType);
-			computeTreeOfTypeReferences(type, variableType);
-			builder.addSiblingNode(variableType);
-		}
-	}
-
-	@Override
-	public void scanCtActualTypeContainer(CtActualTypeContainer reference) {
-		for (CtTypeReference<?> ctTypeArgument : reference.getActualTypeArguments()) {
-			Tree typeArgument = builder.createNode("TYPE_ARGUMENT", ctTypeArgument.getQualifiedName());
-			typeArgument.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, ctTypeArgument);
-			ctTypeArgument.putMetadata(SpoonGumTreeBuilder.GUMTREE_NODE, typeArgument);
-			computeTreeOfTypeReferences(ctTypeArgument, typeArgument);
-			builder.addSiblingNode(typeArgument);
-		}
-	}
-
-	@Override
-	public <T> void scanCtTypeInformation(CtTypeInformation typeReference) {
-		if (typeReference.getSuperInterfaces().isEmpty()) {
-			return;
-		}
-
-		// create the root super interface node whose children will be *actual* spoon
-		// nodes of interfaces
-		Tree superInterfaceRoot = builder.createNode("SUPER_INTERFACES", "");
-		String virtualNodeDescription = "SuperInterfaces_" + typeReference.getQualifiedName();
-		superInterfaceRoot.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, new CtVirtualElement(virtualNodeDescription,
-				(CtElement) typeReference, typeReference.getSuperInterfaces(), CtRole.INTERFACE));
-
-		// attach each super interface to the root created above
-		for (CtTypeReference<?> superInterface : typeReference.getSuperInterfaces()) {
-			Tree superInterfaceNode = builder.createNode("INTERFACE", superInterface.getQualifiedName());
-			superInterfaceNode.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, superInterface);
-			superInterface.putMetadata(SpoonGumTreeBuilder.GUMTREE_NODE, superInterfaceNode);
-			superInterfaceRoot.addChild(superInterfaceNode);
-			computeTreeOfTypeReferences(superInterface, superInterfaceNode);
-		}
-		builder.addSiblingNode(superInterfaceRoot);
-	}
-
-	/**
-	 * Creates a tree of nested type references where each nested type reference is
-	 * a child of its container.
-	 */
-	private void computeTreeOfTypeReferences(CtTypeReference<?> type, Tree parentType) {
-		for (CtTypeReference<?> ctTypeArgument : type.getActualTypeArguments()) {
-			Tree typeArgument = builder.createNode("TYPE_ARGUMENT", ctTypeArgument.getQualifiedName());
-			typeArgument.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, ctTypeArgument);
-			ctTypeArgument.putMetadata(SpoonGumTreeBuilder.GUMTREE_NODE, typeArgument);
-			parentType.addChild(typeArgument);
-			computeTreeOfTypeReferences(ctTypeArgument, typeArgument);
-		}
-	}
-
-	@Override
-	public <T> void visitCtMethod(CtMethod<T> e) {
-		// add the return type of the method
-		CtTypeReference<T> type = e.getType();
-		if (type != null) {
-			Tree returnType = builder.createNode("RETURN_TYPE", type.getQualifiedName());
-			returnType.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, type);
-			type.putMetadata(SpoonGumTreeBuilder.GUMTREE_NODE, returnType);
-			computeTreeOfTypeReferences(type, returnType);
-			builder.addSiblingNode(returnType);
-		}
-
-		if (!e.getThrownTypes().isEmpty()) {
-			Tree thrownTypeRoot = builder.createNode("THROWN_TYPES", "");
-			String virtualNodeDescription = "ThrownTypes_" + e.getSimpleName();
-			thrownTypeRoot.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT,
-					new CtVirtualElement(virtualNodeDescription, e, e.getThrownTypes(), CtRole.THROWN));
-
-			for (CtTypeReference<? extends Throwable> thrownType : e.getThrownTypes()) {
-				Tree thrownNode = builder.createNode("THROWN", thrownType.getQualifiedName());
-				thrownNode.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, thrownType);
-				thrownType.putMetadata(SpoonGumTreeBuilder.GUMTREE_NODE, thrownNode);
-				thrownTypeRoot.addChild(thrownNode);
-			}
-			builder.addSiblingNode(thrownTypeRoot);
-		}
-		super.visitCtMethod(e);
-	}
-
-	@Override
-	public <A extends Annotation> void visitCtAnnotation(CtAnnotation<A> annotation) {
-		if (annotation.getValues().isEmpty()) {
-			return;
-		}
-
-		final String virtualNodeDescription = "AnnotationValues_" + getClassName(annotation.getClass().getSimpleName());
-		Tree annotationNode = builder.createNode(virtualNodeDescription, "");
-
-		annotationNode.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT,
-				new CtVirtualElement(virtualNodeDescription, annotation, annotation.getValues().entrySet(), CtRole.VALUE));
-
-		for (Map.Entry<String, CtExpression> entry: annotation.getValues().entrySet()) {
-			Tree annotationValueNode = builder.createNode("ANNOTATION_VALUE", entry.toString());
-			annotationNode.addChild(annotationValueNode);
-			CtWrapper wrapper = new CtWrapper(entry, annotation, CtRole.VALUE);
-			wrapper.setPosition(entry.getValue().getPosition());
-			annotationValueNode.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, wrapper);
-		}
-		builder.addSiblingNode(annotationNode);
-	}
-
-	@Override
-	public <T> void scanCtTypedElement(CtTypedElement<T> e) {
-		if (e instanceof CtExpression) {
-			CtExpression<?> expression = (CtExpression<?>) e;
-
-			for (CtTypeReference<?> ctTypeCast : expression.getTypeCasts()) {
-				Tree typeCast = builder.createNode("TYPE_CAST", ctTypeCast.getQualifiedName());
-				typeCast.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, ctTypeCast);
-				ctTypeCast.putMetadata(SpoonGumTreeBuilder.GUMTREE_NODE, typeCast);
-				computeTreeOfTypeReferences(ctTypeCast, typeCast);
-				builder.addSiblingNode(typeCast);
-			}
-		}
-	}
+    public static final String MODIFIERS = "Modifiers_";
+    private final TreeScanner builder;
+
+    NodeCreator(TreeScanner builder) {
+        this.builder = builder;
+    }
+
+    private static SourcePosition computeSourcePositionOfVirtualElement(List<SourcePosition> modifierPositions) {
+        CompilationUnit cu = null;
+        Integer sourceStart = null;
+        Integer sourceEnd = null;
+        for (SourcePosition position : modifierPositions) {
+            if (position instanceof NoSourcePosition) {
+                return SourcePosition.NOPOSITION;
+            }
+            if (sourceStart == null || position.getSourceStart() < sourceStart) {
+                sourceStart = position.getSourceStart();
+            }
+            if (sourceEnd == null || position.getSourceEnd() > sourceEnd) {
+                sourceEnd = position.getSourceEnd();
+            }
+            if (cu == null) {
+                cu = position.getCompilationUnit();
+            }
+        }
+        return new SourcePositionImpl(cu, sourceStart, sourceEnd, cu.getLineSeparatorPositions());
+    }
+
+    @Override
+    public void scanCtModifiable(CtModifiable m) {
+
+        if (m.getModifiers().isEmpty())
+            return;
+
+        // We add the type of modifiable element
+        String type = MODIFIERS + getClassName(m.getClass().getSimpleName());
+        Tree modifiers = builder.createNode(type, "");
+        SpoonGumTreeBuilder.setPosition(modifiers, m);
+
+        int modifierStart = Integer.MAX_VALUE;
+        int modifierEnd = Integer.MIN_VALUE;
+        // ensuring an order (instead of hashset)
+        // otherwise some flaky tests in CI
+        Set<CtExtendedModifier> modifiers1 = new TreeSet<>(Comparator.comparing(o -> o.getKind().name()));
+        modifiers1.addAll(m.getExtendedModifiers());
+
+        // we skip creating virtual nodes if all modifiers are implicit
+        // this will not impact diff results as the diff is on source level and implicit modifiers are not in the source
+        if (modifiers1.stream().map(em -> em.getPosition() instanceof NoSourcePosition).allMatch(Boolean.TRUE::equals)) {
+            return;
+        }
+
+        // We create a virtual node
+        CtVirtualElement virtualElement = new CtVirtualElement(type, m, m.getModifiers(), CtRole.MODIFIER);
+        modifiers.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, virtualElement);
+        SpoonGumTreeBuilder.setPosition(modifiers, virtualElement);
+
+        List<SourcePosition> modifierPositions = new ArrayList<>();
+
+        for (CtExtendedModifier mod : modifiers1) {
+            if (mod.isImplicit()) {
+                continue;
+            }
+            modifierStart = Math.min(modifierStart, mod.getPosition().getSourceStart());
+            modifierEnd = Math.max(modifierEnd, mod.getPosition().getSourceEnd());
+            Tree modifier = builder.createNode("Modifier", mod.getKind().toString());
+            modifiers.addChild(modifier);
+            // We wrap the modifier's kind (which is not a CtElement)
+            CtWrapper wrapper = new CtWrapper<>(mod.getKind(), m, CtRole.MODIFIER);
+            wrapper.setPosition(mod.getPosition());
+            modifierPositions.add(mod.getPosition());
+            modifier.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, wrapper);
+            SpoonGumTreeBuilder.setPosition(modifier, wrapper);
+        }
+        modifiers.setPos(modifierStart);
+        modifiers.setLength(modifierEnd - modifierStart);
+        virtualElement.setPosition(computeSourcePositionOfVirtualElement(modifierPositions));
+        builder.addSiblingNode(modifiers);
+    }
+
+    private String getClassName(String simpleName) {
+        if (simpleName == null)
+            return "";
+        return simpleName.replace("Ct", "").replace("Impl", "");
+    }
+
+    @Override
+    public <T> void scanCtVariable(CtVariable<T> e) {
+        CtTypeReference<T> type = e.getType();
+        if (type != null) {
+            Tree variableType = builder.createNode("VARIABLE_TYPE", type.getQualifiedName());
+            variableType.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, type);
+            type.putMetadata(SpoonGumTreeBuilder.GUMTREE_NODE, variableType);
+            computeTreeOfTypeReferences(type, variableType);
+            SpoonGumTreeBuilder.setPosition(variableType, type);
+            builder.addSiblingNode(variableType);
+        }
+    }
+
+    @Override
+    public void scanCtActualTypeContainer(CtActualTypeContainer reference) {
+        for (CtTypeReference<?> ctTypeArgument : reference.getActualTypeArguments()) {
+            Tree typeArgument = builder.createNode("TYPE_ARGUMENT", ctTypeArgument.getQualifiedName());
+            typeArgument.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, ctTypeArgument);
+            ctTypeArgument.putMetadata(SpoonGumTreeBuilder.GUMTREE_NODE, typeArgument);
+            computeTreeOfTypeReferences(ctTypeArgument, typeArgument);
+            SpoonGumTreeBuilder.setPosition(typeArgument, ctTypeArgument);
+            builder.addSiblingNode(typeArgument);
+        }
+    }
+
+    @Override
+    public <T> void scanCtTypeInformation(CtTypeInformation typeReference) {
+        if (typeReference.getSuperInterfaces().isEmpty()) {
+            return;
+        }
+
+        // create the root super interface node whose children will be *actual* spoon
+        // nodes of interfaces
+        Tree superInterfaceRoot = builder.createNode("SUPER_INTERFACES", "");
+        String virtualNodeDescription = "SuperInterfaces_" + typeReference.getQualifiedName();
+        CtVirtualElement superInterfaceVirtualElement = new CtVirtualElement(virtualNodeDescription, (CtElement) typeReference,
+                typeReference.getSuperInterfaces(), CtRole.INTERFACE);
+        superInterfaceRoot.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, superInterfaceVirtualElement);
+
+        List<SourcePosition> superInterfacePositions = new ArrayList<>();
+        // attach each super interface to the root created above
+        for (CtTypeReference<?> superInterface : typeReference.getSuperInterfaces()) {
+            Tree superInterfaceNode = builder.createNode("INTERFACE", superInterface.getQualifiedName());
+            superInterfaceNode.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, superInterface);
+            superInterface.putMetadata(SpoonGumTreeBuilder.GUMTREE_NODE, superInterfaceNode);
+            SpoonGumTreeBuilder.setPosition(superInterfaceNode, superInterface);
+            superInterfaceRoot.addChild(superInterfaceNode);
+            superInterfacePositions.add(superInterface.getPosition());
+            computeTreeOfTypeReferences(superInterface, superInterfaceNode);
+        }
+        superInterfaceVirtualElement.setPosition(computeSourcePositionOfVirtualElement(superInterfacePositions));
+        SpoonGumTreeBuilder.setPosition(superInterfaceRoot, superInterfaceVirtualElement);
+        builder.addSiblingNode(superInterfaceRoot);
+    }
+
+    /**
+     * Creates a tree of nested type references where each nested type reference is
+     * a child of its container.
+     */
+    private void computeTreeOfTypeReferences(CtTypeReference<?> type, Tree parentType) {
+        for (CtTypeReference<?> ctTypeArgument : type.getActualTypeArguments()) {
+            Tree typeArgument = builder.createNode("TYPE_ARGUMENT", ctTypeArgument.getQualifiedName());
+            typeArgument.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, ctTypeArgument);
+            ctTypeArgument.putMetadata(SpoonGumTreeBuilder.GUMTREE_NODE, typeArgument);
+            SpoonGumTreeBuilder.setPosition(typeArgument, ctTypeArgument);
+            parentType.addChild(typeArgument);
+            computeTreeOfTypeReferences(ctTypeArgument, typeArgument);
+        }
+    }
+
+    @Override
+    public <T> void visitCtMethod(CtMethod<T> e) {
+        // add the return type of the method
+        CtTypeReference<T> type = e.getType();
+        if (type != null) {
+            Tree returnType = builder.createNode("RETURN_TYPE", type.getQualifiedName());
+            returnType.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, type);
+            type.putMetadata(SpoonGumTreeBuilder.GUMTREE_NODE, returnType);
+            computeTreeOfTypeReferences(type, returnType);
+            SpoonGumTreeBuilder.setPosition(returnType, type);
+            builder.addSiblingNode(returnType);
+        }
+
+        if (!e.getThrownTypes().isEmpty()) {
+            Tree thrownTypeRoot = builder.createNode("THROWN_TYPES", "");
+            String virtualNodeDescription = "ThrownTypes_" + e.getSimpleName();
+            CtVirtualElement thrownTypeVirtualElement = new CtVirtualElement(virtualNodeDescription, e, e.getThrownTypes(), CtRole.THROWN);
+            thrownTypeRoot.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT,
+                    thrownTypeVirtualElement);
+            List<SourcePosition> thrownTypePositions = new ArrayList<>();
+
+            for (CtTypeReference<? extends Throwable> thrownType : e.getThrownTypes()) {
+                Tree thrownNode = builder.createNode("THROWN", thrownType.getQualifiedName());
+                thrownNode.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, thrownType);
+                thrownType.putMetadata(SpoonGumTreeBuilder.GUMTREE_NODE, thrownNode);
+                SpoonGumTreeBuilder.setPosition(thrownNode, thrownType);
+                thrownTypeRoot.addChild(thrownNode);
+                thrownTypePositions.add(thrownType.getPosition());
+            }
+            thrownTypeVirtualElement.setPosition(computeSourcePositionOfVirtualElement(thrownTypePositions));
+            SpoonGumTreeBuilder.setPosition(thrownTypeRoot, thrownTypeVirtualElement);
+            builder.addSiblingNode(thrownTypeRoot);
+        }
+        super.visitCtMethod(e);
+    }
+
+    @Override
+    public <A extends Annotation> void visitCtAnnotation(CtAnnotation<A> annotation) {
+        if (annotation.getValues().isEmpty()) {
+            return;
+        }
+
+        final String virtualNodeDescription = "AnnotationValues_" + getClassName(annotation.getClass().getSimpleName());
+        Tree annotationNode = builder.createNode(virtualNodeDescription, "");
+
+        CtVirtualElement annotationVirtualElement = new CtVirtualElement(virtualNodeDescription, annotation, annotation.getValues().entrySet(), CtRole.VALUE);
+        annotationNode.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT,
+                annotationVirtualElement);
+
+        List<SourcePosition> annotationPositions = new ArrayList<>();
+        for (Map.Entry<String, CtExpression> entry : annotation.getValues().entrySet()) {
+            Tree annotationValueNode = builder.createNode("ANNOTATION_VALUE", entry.toString());
+            annotationNode.addChild(annotationValueNode);
+            CtWrapper wrapper = new CtWrapper(entry, annotation, CtRole.VALUE);
+            wrapper.setPosition(entry.getValue().getPosition());
+            annotationValueNode.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, wrapper);
+            SpoonGumTreeBuilder.setPosition(annotationValueNode, entry.getValue());
+            annotationPositions.add(entry.getValue().getPosition());
+        }
+        annotationVirtualElement.setPosition(computeSourcePositionOfVirtualElement(annotationPositions));
+        SpoonGumTreeBuilder.setPosition(annotationNode, annotationVirtualElement);
+
+        builder.addSiblingNode(annotationNode);
+    }
+
+    @Override
+    public <T> void scanCtTypedElement(CtTypedElement<T> e) {
+        if (e instanceof CtExpression) {
+            CtExpression<?> expression = (CtExpression<?>) e;
+
+            for (CtTypeReference<?> ctTypeCast : expression.getTypeCasts()) {
+                Tree typeCast = builder.createNode("TYPE_CAST", ctTypeCast.getQualifiedName());
+                typeCast.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, ctTypeCast);
+                ctTypeCast.putMetadata(SpoonGumTreeBuilder.GUMTREE_NODE, typeCast);
+                computeTreeOfTypeReferences(ctTypeCast, typeCast);
+                SpoonGumTreeBuilder.setPosition(typeCast, ctTypeCast);
+                builder.addSiblingNode(typeCast);
+            }
+        }
+    }
 }
diff --git a/src/main/java/gumtree/spoon/builder/SpoonGumTreeBuilder.java b/src/main/java/gumtree/spoon/builder/SpoonGumTreeBuilder.java
index eb35fdf..caf53d5 100644
--- a/src/main/java/gumtree/spoon/builder/SpoonGumTreeBuilder.java
+++ b/src/main/java/gumtree/spoon/builder/SpoonGumTreeBuilder.java
@@ -23,6 +23,7 @@ import com.github.gumtreediff.tree.Tree;
 import com.github.gumtreediff.tree.TreeContext;
 import com.github.gumtreediff.tree.Type;
 
+import spoon.reflect.cu.position.NoSourcePosition;
 import spoon.reflect.declaration.CtElement;
 
 /**
@@ -40,6 +41,7 @@ public class SpoonGumTreeBuilder {
 	public Tree getTree(CtElement element) {
 		Type type = type("root");
 		final Tree root = treeContext.createTree(type, "");
+		setPosition(root, element);
 		new TreeScanner(treeContext, root).scan(element);
 		return root;
 	}
@@ -47,4 +49,14 @@ public class SpoonGumTreeBuilder {
 	public TreeContext getTreeContext() {
 		return treeContext;
 	}
+
+	public static void setPosition(Tree node, CtElement element) {
+		if (element.getPosition() instanceof NoSourcePosition) {
+			return;
+		}
+		int startPosition = element.getPosition().getSourceStart();
+		int endPosition = element.getPosition().getSourceEnd();
+		node.setPos(startPosition);
+		node.setLength(endPosition - startPosition);
+	}
 }
diff --git a/src/main/java/gumtree/spoon/builder/TreeScanner.java b/src/main/java/gumtree/spoon/builder/TreeScanner.java
index 6bb33e2..d327b45 100644
--- a/src/main/java/gumtree/spoon/builder/TreeScanner.java
+++ b/src/main/java/gumtree/spoon/builder/TreeScanner.java
@@ -135,6 +135,7 @@ public class TreeScanner extends CtScanner {
 		Tree newNode = createNode(nodeTypeName, label);
 		newNode.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, element);
 		element.putMetadata(SpoonGumTreeBuilder.GUMTREE_NODE, newNode);
+		SpoonGumTreeBuilder.setPosition(newNode, element);
 		return newNode;
 	}
 


Test patch:
diff --git a/src/test/java/gumtree/spoon/SourcePositionTest.java b/src/test/java/gumtree/spoon/SourcePositionTest.java
new file mode 100644
index 0000000..8db12af
--- /dev/null
+++ b/src/test/java/gumtree/spoon/SourcePositionTest.java
@@ -0,0 +1,134 @@
+package gumtree.spoon;
+
+import com.github.gumtreediff.tree.Tree;
+import gumtree.spoon.builder.SpoonGumTreeBuilder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import spoon.Launcher;
+import spoon.reflect.code.CtTypeAccess;
+import spoon.reflect.declaration.CtElement;
+import spoon.reflect.declaration.CtPackage;
+
+import java.io.File;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.stream.Collectors;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(Parameterized.class)
+public class SourcePositionTest {
+
+    private File javaSourceFile;
+    private String fileName;
+
+    public SourcePositionTest(File javaSourceFile, String fileName) {
+        this.javaSourceFile = javaSourceFile;
+        this.fileName = fileName;
+    }
+
+    @Parameterized.Parameters(name = "{1}")
+    public static Collection<Object[]> data() {
+        File directory = new File("src/test/resources/source-positions");
+        return Arrays.stream(Objects.requireNonNull(directory.listFiles())).map(file -> new Object[]{file, file.getName()}).collect(Collectors.toUnmodifiableList());
+    }
+
+    private static Tree getRootNode(File javaSourceFile) {
+        Launcher launcher = new Launcher();
+        launcher.addInputResource(javaSourceFile.getAbsolutePath());
+        launcher.buildModel();
+        CtPackage rootPackage = launcher.getModel().getRootPackage();
+        return new SpoonGumTreeBuilder().getTree(rootPackage);
+    }
+
+    private static boolean ignoreNodes(Tree node) {
+        // they don't have a position in the Spoon model
+        CtElement element = (CtElement) node.getMetadata(SpoonGumTreeBuilder.SPOON_OBJECT);
+        if (element instanceof CtPackage) {
+            return true;
+        }
+        if (element instanceof CtTypeAccess) {
+            return true;
+        }
+        return false;
+    }
+
+    private static boolean siblingsDoNotContainEachOther(List<Tree> siblings) {
+        for (int i = 0; i < siblings.size(); i++) {
+            Tree sibling1 = siblings.get(i);
+            for (int j = i + 1; j < siblings.size(); j++) {
+                Tree sibling2 = siblings.get(j);
+                if (ignoreNodes(sibling1) || ignoreNodes(sibling2)) {
+                    continue;
+                }
+                int startPosition1 = sibling1.getPos();
+                int endPosition1 = startPosition1 + sibling1.getLength();
+                int startPosition2 = sibling2.getPos();
+                int endPosition2 = startPosition2 + sibling2.getLength();
+                if (startPosition2 <= startPosition1 && endPosition2 >= endPosition1) {
+                    throw new AssertionError(sibling1 + " and " + sibling2 + " are overlapping");
+                }
+                if (startPosition1 <= startPosition2 && endPosition2 <= endPosition1) {
+                    throw new AssertionError(sibling1 + " and " + sibling2 + " are overlapping");
+                }
+                if (startPosition1 < startPosition2 && endPosition1 > startPosition2) {
+                    throw new AssertionError(sibling1 + " and " + sibling2 + " are overlapping");
+                }
+                if (startPosition2 < startPosition1 && endPosition2 > startPosition1) {
+                    throw new AssertionError(sibling1 + " and " + sibling2 + " are overlapping");
+                }
+            }
+        }
+        return true;
+    }
+
+    @Test
+    public void childNodeMustBeEnclosedInParentNode() {
+        // Arrange
+        Tree rootNode = getRootNode(javaSourceFile);
+        // Assert
+        for (Tree child : rootNode.getDescendants()) {
+            Tree parent = child.getParent();
+            if (ignoreNodes(child) || ignoreNodes(parent)) {
+                continue;
+            }
+            int startPosition = child.getPos();
+            int endPosition = startPosition + child.getLength();
+            assertThat(child + " must be enclosed in " + rootNode,
+                    startPosition >= parent.getPos() && endPosition <= parent.getPos() + parent.getLength());
+        }
+    }
+
+    @Test
+    public void allNodesShouldHaveAtLeastZeroLength() {
+        // Arrange
+        Tree rootNode = getRootNode(javaSourceFile);
+        // Assert
+        for (Tree node : rootNode.getDescendants()) {
+            if (ignoreNodes(node)) {
+                continue;
+            }
+            assertTrue(node + " has negative length", node.getLength() >= 0);
+        }
+    }
+
+    @Test
+    public void siblingNodeShouldNotContainEachOther() {
+        // Arrange
+        Tree rootNode = getRootNode(javaSourceFile);
+        // Assert
+        Queue<Tree> roots = new ArrayDeque<>();
+        roots.add(rootNode);
+        while (!roots.isEmpty()) {
+            List<Tree> siblings = roots.poll().getChildren();
+            assertTrue(siblingsDoNotContainEachOther(siblings));
+            roots.addAll(siblings);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/gumtree/spoon/TreeTest.java b/src/test/java/gumtree/spoon/TreeTest.java
index 927a86d..e7996f7 100644
--- a/src/test/java/gumtree/spoon/TreeTest.java
+++ b/src/test/java/gumtree/spoon/TreeTest.java
@@ -20,7 +20,6 @@ package gumtree.spoon;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 import java.io.File;
 import java.util.List;
diff --git a/src/test/resources/source-positions/Annotations.java b/src/test/resources/source-positions/Annotations.java
new file mode 100644
index 0000000..a882f4a
--- /dev/null
+++ b/src/test/resources/source-positions/Annotations.java
@@ -0,0 +1,8 @@
+import org.junit.Ignore;
+
+import java.beans.JavaBean;
+
+@Ignore("This is a test class")
+@JavaBean(description = "This is a test class", defaultProperty = "name")
+public class Annotations {
+}
\ No newline at end of file
diff --git a/src/test/resources/source-positions/Field.java b/src/test/resources/source-positions/Field.java
new file mode 100644
index 0000000..6cbea64
--- /dev/null
+++ b/src/test/resources/source-positions/Field.java
@@ -0,0 +1,3 @@
+class Field {
+    private int x = 0;
+}
diff --git a/src/test/resources/source-positions/Return.java b/src/test/resources/source-positions/Return.java
new file mode 100644
index 0000000..f69b5ba
--- /dev/null
+++ b/src/test/resources/source-positions/Return.java
@@ -0,0 +1,5 @@
+class Return {
+    int foo() {
+        return 42;
+    }
+}
diff --git a/src/test/resources/source-positions/SuperInterface.java b/src/test/resources/source-positions/SuperInterface.java
new file mode 100644
index 0000000..2e25028
--- /dev/null
+++ b/src/test/resources/source-positions/SuperInterface.java
@@ -0,0 +1,7 @@
+package x;
+
+public class SuperInterface implements Runnable {
+    public void run() {
+        System.out.println("Hello, World!");
+    }
+}
diff --git a/src/test/resources/source-positions/ThrownType.java b/src/test/resources/source-positions/ThrownType.java
new file mode 100644
index 0000000..d63bd75
--- /dev/null
+++ b/src/test/resources/source-positions/ThrownType.java
@@ -0,0 +1,8 @@
+import java.io.IOException;
+
+public class ThrownType {
+    public void iThrow(String[] args) throws IndexOutOfBoundsException, IOException {
+        String s = args[42];
+        throw new IOException();
+    }
+}
\ No newline at end of file
diff --git a/src/test/resources/source-positions/TypeAccess.java b/src/test/resources/source-positions/TypeAccess.java
new file mode 100644
index 0000000..7c00fc0
--- /dev/null
+++ b/src/test/resources/source-positions/TypeAccess.java
@@ -0,0 +1,9 @@
+public class ActualTypeContainer<A, B> {
+    private A a;
+    private B b;
+
+    public ActualTypeContainer(A a, B b) {
+        this.a = a;
+        this.b = b;
+    }
+}
diff --git a/src/test/resources/source-positions/TypeArgument.java b/src/test/resources/source-positions/TypeArgument.java
new file mode 100644
index 0000000..5c49adc
--- /dev/null
+++ b/src/test/resources/source-positions/TypeArgument.java
@@ -0,0 +1,5 @@
+import java.util.List;
+
+public class TypeArgument {
+    private List<List<String>> list;
+}
diff --git a/src/test/resources/source-positions/TypeCast.java b/src/test/resources/source-positions/TypeCast.java
new file mode 100644
index 0000000..ae2daf7
--- /dev/null
+++ b/src/test/resources/source-positions/TypeCast.java
@@ -0,0 +1,7 @@
+public class TypeCast {
+    public static void main(String[] args) {
+        double d = 10.0;
+        int i = (int) d;
+        System.out.println(i);
+    }
+}
diff --git a/src/test/resources/source-positions/VariableType.java b/src/test/resources/source-positions/VariableType.java
new file mode 100644
index 0000000..a098514
--- /dev/null
+++ b/src/test/resources/source-positions/VariableType.java
@@ -0,0 +1,7 @@
+class VariableType {
+    public static void main(String[] args) {
+        int a = 142343;
+        int b = 2;
+        int c = a + b;
+    }
+}