code: private void rtrimPositions(List<Node> nodes, List<Comment> comments) {
  		final boolean[] whitespace = new boolean[preprocessed.length()];
  		for (Comment comment : comments) {
  			Position p = comment.getPosition();
  			if (!p.isUnplaced()) {
  				for (int i = p.getStart(); i < p.getEnd(); i++) whitespace[i] = true;
  			}
  		}
  		
  		/* Process actual whitespace in preprocessed source data */ {
  			char[] chars = preprocessed.toCharArray();
  			for (int i = 0; i < chars.length; i++) if (Character.isWhitespace(chars[i])) whitespace[i] = true;
  		}
  		
  		for (Node node : nodes) node.accept(new ForwardingAstVisitor() {
  			@Override public boolean visitNode(Node node) {
  				Position p = node.getPosition();
  				if (p.isUnplaced()) return false;
  				
  				int trimmed = Math.min(whitespace.length, p.getEnd());
  				while (trimmed > 0 && whitespace[trimmed-1]) trimmed--;
  				
  				int start, end;
  				
  				if (p.getEnd() - p.getStart() == 0) {
  					if (node.getParent() != null) {
  						start = Math.min(node.getParent().getPosition().getEnd(), Math.max(node.getParent().getPosition().getStart(), p.getStart()));
  						end = start;
  					} else {
  						start = p.getStart();
  						end = start;
  					}
  				} else {
  					start = p.getStart();
  					end = Math.max(trimmed, start);
  				}
  				
  				node.setPosition(new Position(start, end));
  				
  				return false;
  			}
  		});
  	}
docs: The end positions of all nodes include their trailing whitespace which isn't very convenient.
  We'll 'fix' the end marker of each node by trimming it back. This is somewhat complicated as comments also need to be trimmed across.
  We also adjust all positions to conform with the raw input (undoing any positional shifts caused by preprocessing).