"
13 | exit 1
14 | fi
15 |
16 | git submodule deinit --force "$SUBMODULE"
17 | git rm --cached "$SUBMODULE"
18 | git config -f .gitmodules --remove-section "submodule.$SUBMODULE"
19 | git add .gitmodules
20 | rm -rf ".git/modules/$SUBMODULE" "$SUBMODULE"
21 | git commit --message "Remove \`$SUBMODULE\` submodule"
22 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | GitPython==3.1.44
2 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/Capture.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.Generated;
5 | import lombok.Getter;
6 | import lombok.RequiredArgsConstructor;
7 | import lombok.experimental.FieldDefaults;
8 | import lombok.experimental.NonFinal;
9 | import org.jetbrains.annotations.NotNull;
10 |
11 | import java.util.List;
12 | import java.util.Objects;
13 | import java.util.stream.Collectors;
14 |
15 | /**
16 | * Represents the named capture of a {@link Query}. Captures are used
17 | * to extract information from syntax trees when a query match occurs.
18 | * Each instance can be uniquely identified by the {@link Query} it
19 | * belongs to, along with its ordinal position within the same query.
20 | *
21 | * @since 1.7.0
22 | * @author Ozren Dabić
23 | * @see Pattern
24 | * @see Query
25 | */
26 | @Getter
27 | @RequiredArgsConstructor(access = AccessLevel.PACKAGE)
28 | @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
29 | public class Capture {
30 |
31 | Query query;
32 |
33 | int index;
34 |
35 | String name;
36 |
37 | @NonFinal
38 | boolean enabled = true;
39 |
40 | @SuppressWarnings("unused")
41 | Capture(int index, @NotNull String name) {
42 | this(null, index, name);
43 | }
44 |
45 | /**
46 | * Disable this capture, preventing it from returning in matches.
47 | * This will eliminate any resource usage from the {@link Query}
48 | * associated with recording the capture.
49 | *
50 | *
51 | * This can not be undone.
52 | *
53 | */
54 | public native void disable();
55 |
56 | /**
57 | * Get the capture quantifier for a given query {@link Pattern}.
58 | *
59 | * @param pattern the query pattern
60 | * @return the quantifier
61 | * @throws NullPointerException if pattern is {@code null}
62 | * @throws IllegalArgumentException if the pattern is not present in the query
63 | * @since 1.12.0
64 | */
65 | public Quantifier getQuantifier(@NotNull Pattern pattern) {
66 | return query.getQuantifier(pattern, this);
67 | }
68 |
69 | /**
70 | * Get the capture quantifiers for all {@link Query} patterns.
71 | * The order of the quantifiers in the returned list corresponds
72 | * to the {@link Pattern} order in the query.
73 | *
74 | * @return the quantifiers
75 | * @since 1.12.0
76 | */
77 | public List getQuantifiers() {
78 | return query.getPatterns().stream()
79 | .map(this::getQuantifier)
80 | .collect(Collectors.toUnmodifiableList());
81 | }
82 |
83 | @Override
84 | @Generated
85 | public boolean equals(Object o) {
86 | if (this == o) return true;
87 | if (o == null || getClass() != o.getClass()) return false;
88 | Capture capture = (Capture) o;
89 | return Objects.equals(query, capture.query) && index == capture.index;
90 | }
91 |
92 | @Override
93 | @Generated
94 | public int hashCode() {
95 | return Objects.hash(query, index);
96 | }
97 |
98 | @Override
99 | @Generated
100 | public String toString() {
101 | return "@" + name;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/External.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.AllArgsConstructor;
5 | import lombok.NoArgsConstructor;
6 | import lombok.experimental.FieldDefaults;
7 |
8 | import java.lang.ref.Cleaner;
9 |
10 | @FieldDefaults(makeFinal = true)
11 | @NoArgsConstructor(access = AccessLevel.PROTECTED, force = true)
12 | abstract class External implements AutoCloseable {
13 |
14 | protected long pointer;
15 | private Cleaner.Cleanable cleanable;
16 |
17 | private static final Cleaner CLEANER = Cleaner.create();
18 |
19 | protected External(long pointer) {
20 | this.pointer = pointer;
21 | this.cleanable = CLEANER.register(this, new Action(this));
22 | }
23 |
24 | /**
25 | * Checks whether the memory address associated
26 | * with this external resource is {@code nullptr}.
27 | *
28 | * @return {@code true} if the memory address is 0,
29 | * otherwise {@code false}
30 | */
31 | public final boolean isNull() {
32 | return pointer == 0;
33 | }
34 |
35 | @Override
36 | public boolean equals(Object obj) {
37 | if (obj == this) return true;
38 | if (obj == null || getClass() != obj.getClass()) return false;
39 | External other = (External) obj;
40 | return pointer == other.pointer;
41 | }
42 |
43 | @Override
44 | public int hashCode() {
45 | return Long.hashCode(pointer);
46 | }
47 |
48 | /**
49 | * Delete the external resource, freeing all the memory that it used.
50 | */
51 | @Override
52 | public void close() {
53 | boolean requiresCleaning = cleanable != null && !isNull();
54 | if (requiresCleaning) cleanable.clean();
55 | }
56 |
57 | protected abstract void delete();
58 |
59 | @AllArgsConstructor(access = AccessLevel.PRIVATE)
60 | @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
61 | private static final class Action implements Runnable {
62 |
63 | External external;
64 |
65 | @Override
66 | public void run() {
67 | external.delete();
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/InputEdit.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.AllArgsConstructor;
5 | import lombok.Getter;
6 | import lombok.experimental.FieldDefaults;
7 |
8 | /**
9 | * Represents an edit operation on a section of source code.
10 | * Contains information pertaining to the starting byte offset and position,
11 | * as well as the former and current end byte offsets and positions.
12 | *
13 | * @since 1.0.0
14 | * @author Ozren Dabić
15 | */
16 | @Getter
17 | @AllArgsConstructor
18 | @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
19 | public class InputEdit {
20 |
21 | int startByte;
22 | int oldEndByte;
23 | int newEndByte;
24 | Point startPoint;
25 | Point oldEndPoint;
26 | Point newEndPoint;
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/LibraryLoader.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import ch.usi.si.seart.treesitter.exception.TreeSitterException;
4 | import lombok.AccessLevel;
5 | import lombok.AllArgsConstructor;
6 | import lombok.experimental.FieldDefaults;
7 | import lombok.experimental.UtilityClass;
8 | import org.apache.commons.io.FileUtils;
9 | import org.apache.commons.io.IOUtils;
10 | import org.apache.commons.lang3.SystemUtils;
11 |
12 | import java.io.File;
13 | import java.io.FileOutputStream;
14 | import java.io.IOException;
15 | import java.io.InputStream;
16 | import java.io.OutputStream;
17 | import java.net.URL;
18 |
19 | /**
20 | * Utility for loading the native system library.
21 | *
22 | * @since 1.0.0
23 | * @author Ozren Dabić
24 | */
25 | @UtilityClass
26 | public class LibraryLoader {
27 |
28 | private static final String LIBRARY_FILE_NAME = "libjava-tree-sitter";
29 |
30 | @AllArgsConstructor(access = AccessLevel.PRIVATE)
31 | @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
32 | private static final class SystemResource {
33 |
34 | URL url;
35 | String name;
36 |
37 | private SystemResource(String name) {
38 | this(LibraryLoader.class.getClassLoader().getResource(name), name);
39 | }
40 | }
41 |
42 | /**
43 | * Load the native library.
44 | * Call this once prior to using any of the APIs.
45 | */
46 | public void load() {
47 | String filename = String.format("%s.%s", LIBRARY_FILE_NAME, getExtension());
48 | SystemResource systemResource = new SystemResource(filename);
49 | String libPath = getLibPath(systemResource);
50 | System.load(libPath);
51 | }
52 |
53 | private String getLibPath(SystemResource systemResource) {
54 | String protocol = systemResource.url.getProtocol();
55 | switch (protocol) {
56 | case "file":
57 | return systemResource.url.getPath();
58 | case "jar":
59 | File tmpdir = FileUtils.getTempDirectory();
60 | File tmpfile = new File(tmpdir, systemResource.name);
61 | tmpfile.deleteOnExit();
62 | try (
63 | InputStream input = systemResource.url.openStream();
64 | OutputStream output = new FileOutputStream(tmpfile, false)
65 | ) {
66 | IOUtils.copy(input, output);
67 | return tmpfile.getPath();
68 | } catch (IOException cause) {
69 | throw new TreeSitterException(cause);
70 | }
71 | default:
72 | Exception cause = new UnsupportedOperationException("Unsupported protocol: " + protocol);
73 | throw new TreeSitterException(cause);
74 | }
75 | }
76 |
77 | private String getExtension() {
78 | if (SystemUtils.IS_OS_LINUX) return "so";
79 | else if (SystemUtils.IS_OS_MAC) return "dylib";
80 | else throw new TreeSitterException(
81 | "The tree-sitter library was not compiled for this platform: " + SystemUtils.OS_NAME.toLowerCase()
82 | );
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/LookaheadIterator.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import lombok.Getter;
4 | import lombok.experimental.FieldDefaults;
5 |
6 | import java.util.Iterator;
7 |
8 | /**
9 | * Specialized iterator that can be used to generate suggestions and improve syntax error diagnostics.
10 | * To get symbols valid in an {@code ERROR} node, use the lookahead iterator on its first leaf node state.
11 | * For {@code MISSING} nodes, a lookahead iterator created on the previous non-extra leaf node may be appropriate.
12 | *
13 | * @since 1.12.0
14 | * @author Ozren Dabić
15 | */
16 | @FieldDefaults(level = lombok.AccessLevel.PRIVATE, makeFinal = true)
17 | public class LookaheadIterator extends External implements Iterator {
18 |
19 | boolean hasNext;
20 |
21 | @Getter
22 | Language language;
23 |
24 | LookaheadIterator(long pointer, boolean hasNext, Language language) {
25 | super(pointer);
26 | this.hasNext = hasNext;
27 | this.language = language;
28 | }
29 |
30 | @Override
31 | protected native void delete();
32 |
33 | @Override
34 | public boolean hasNext() {
35 | return hasNext;
36 | }
37 |
38 | @Override
39 | public native Symbol next();
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/Pattern.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.Generated;
5 | import lombok.Getter;
6 | import lombok.RequiredArgsConstructor;
7 | import lombok.experimental.FieldDefaults;
8 | import lombok.experimental.NonFinal;
9 | import org.jetbrains.annotations.NotNull;
10 |
11 | import java.util.List;
12 | import java.util.Objects;
13 |
14 | /**
15 | * Represents a single symbolic expression (s-expression) pattern of a {@link Query}.
16 | * Said pattern is a structured representation of a syntax tree fragment, which can
17 | * be used to query subtrees. Each instance can be uniquely identified by the query
18 | * it belongs to, along with its ordinal position within the same query.
19 | *
20 | * @since 1.7.0
21 | * @author Ozren Dabić
22 | * @see Capture
23 | * @see Query
24 | */
25 | @Getter
26 | @RequiredArgsConstructor(access = AccessLevel.PACKAGE)
27 | @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
28 | public class Pattern {
29 |
30 | Query query;
31 |
32 | int index;
33 |
34 | int startOffset;
35 |
36 | boolean rooted;
37 | boolean nonLocal;
38 |
39 | String value;
40 |
41 | List predicates;
42 |
43 | @NonFinal
44 | boolean enabled = true;
45 |
46 | @SuppressWarnings("unused")
47 | Pattern(
48 | int index,
49 | int startOffset,
50 | boolean rooted,
51 | boolean nonLocal,
52 | @NotNull String value,
53 | @NotNull Predicate[] predicates
54 | ) {
55 | this(null, index, startOffset, rooted, nonLocal, value.stripTrailing(), List.of(predicates));
56 | }
57 |
58 | /**
59 | * Disable this pattern, preventing it from further matching.
60 | * This will eliminate any resource usage from the {@link Query}
61 | * associated with the pattern.
62 | *
63 | *
64 | * This can not be undone.
65 | *
66 | */
67 | public native void disable();
68 |
69 | @Generated
70 | public int getEndOffset() {
71 | return startOffset + value.length();
72 | }
73 |
74 | @Override
75 | @Generated
76 | public boolean equals(Object o) {
77 | if (this == o) return true;
78 | if (o == null || getClass() != o.getClass()) return false;
79 | Pattern pattern = (Pattern) o;
80 | return Objects.equals(query, pattern.query) && index == pattern.index;
81 | }
82 |
83 | @Override
84 | @Generated
85 | public int hashCode() {
86 | return Objects.hash(query, index);
87 | }
88 |
89 | @Override
90 | @Generated
91 | public String toString() {
92 | return value;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/Point.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.AllArgsConstructor;
5 | import lombok.EqualsAndHashCode;
6 | import lombok.Generated;
7 | import lombok.Getter;
8 | import lombok.experimental.Accessors;
9 | import lombok.experimental.FieldDefaults;
10 | import org.jetbrains.annotations.NotNull;
11 |
12 | import java.util.Objects;
13 |
14 | /**
15 | * Represents a two-dimensional point with row and column coordinates.
16 | * Points are an alternative to byte ranges, and as such are used to
17 | * represent more human-friendly positions of {@link Tree} nodes within
18 | * source code. Although {@link Node} positions within files should never
19 | * be negative, instances of this class can be created with negative row
20 | * and column values for other purposes, such as representing relative
21 | * positions within a file or snippet.
22 | *
23 | * Points are immutable, and operations on them will either yield a new
24 | * instance, or existing instances under certain conditions. For example,
25 | * adding the origin point to any other point will return the same instance.
26 | *
27 | * @since 1.0.0
28 | * @author Ozren Dabić
29 | */
30 | @Getter
31 | @AllArgsConstructor
32 | @EqualsAndHashCode
33 | @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
34 | public class Point implements Comparable {
35 |
36 | @Getter(lazy = true)
37 | @Accessors(fluent = true, makeFinal = true)
38 | private static final Point ORIGIN = new Point(0, 0);
39 |
40 | int row;
41 | int column;
42 |
43 | /**
44 | * Returns a string representation of this point in the format:
45 | * {@code
46 | * row:column
47 | * }
48 | *
49 | * @return a string representation of this point
50 | */
51 | @Override
52 | @Generated
53 | public String toString() {
54 | return row + ":" + column;
55 | }
56 |
57 | /**
58 | * Checks if this point represents the origin,
59 | * which is when both the row and the column are zero.
60 | * In byte range terms, this point also corresponds to zero.
61 | *
62 | * @return {@code true} if this is an origin point,
63 | * {@code false} otherwise
64 | */
65 | public boolean isOrigin() {
66 | return equals(ORIGIN());
67 | }
68 |
69 | /**
70 | * Compares this point with another point for positional order.
71 | * Points are compared by their row values first, and if those
72 | * are equal they are then compared by their column values.
73 | *
74 | * @param other the object to be compared
75 | * @return the row comparison result, or the column comparison result if the rows are equal
76 | * @throws NullPointerException if the other point is null
77 | * @since 1.5.1
78 | */
79 | @Override
80 | public int compareTo(@NotNull Point other) {
81 | Objects.requireNonNull(other, "Other point must not be null!");
82 | int compare = Integer.compare(row, other.row);
83 | return compare != 0 ? compare : Integer.compare(column, other.column);
84 | }
85 |
86 | /**
87 | * Adds another point to this point,
88 | * resulting in a new point with coordinates
89 | * equal to the sum of the coordinates
90 | * of this point and the other point.
91 | *
92 | * @param other the point to be added to this point
93 | * @return the resulting point
94 | * @throws NullPointerException if {@code other} is null
95 | * @since 1.5.1
96 | */
97 | public Point add(@NotNull Point other) {
98 | Objects.requireNonNull(other, "Other point must not be null!");
99 | if (isOrigin()) return other;
100 | if (other.isOrigin()) return this;
101 | return add(other.row, other.column);
102 | }
103 |
104 | /**
105 | * Subtracts another point from this point, resulting
106 | * in a new point with coordinates equal to the difference
107 | * between the coordinates of this point and the other point.
108 | *
109 | * @param other the point to be subtracted from this point
110 | * @return the resulting point
111 | * @throws NullPointerException if {@code other} is null
112 | * @since 1.5.1
113 | */
114 | public Point subtract(@NotNull Point other) {
115 | Objects.requireNonNull(other, "Other point must not be null!");
116 | if (other.isOrigin()) return this;
117 | if (equals(other)) return ORIGIN();
118 | return add(-other.row, -other.column);
119 | }
120 |
121 | private Point add(int row, int column) {
122 | return new Point(this.row + row, this.column + column);
123 | }
124 |
125 | /**
126 | * Multiplies the coordinates of this point by a scalar value,
127 | * resulting in a new point with scaled coordinates.
128 | *
129 | * @param value the scalar value by which to multiply the coordinates of this point
130 | * @return the resulting point
131 | * @since 1.5.1
132 | */
133 | public Point multiply(int value) {
134 | switch (value) {
135 | case 0: return ORIGIN();
136 | case 1: return this;
137 | default: return new Point(row * value, column * value);
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/Predicate.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.AllArgsConstructor;
5 | import lombok.Getter;
6 | import lombok.experimental.FieldDefaults;
7 |
8 | import java.util.List;
9 |
10 | /**
11 | * A specialised symbolic expression (s-expression) that can
12 | * appear anywhere within a {@link Pattern}, instances of this
13 | * class represent predicates within a pattern. They consist of
14 | * arbitrary metadata and conditions which dictate the matching
15 | * behavior of a {@link Query}.
16 | *
17 | * Note that the actual matching behavior is currently not
18 | * implemented in this binding. In spite of this, one can
19 | * still use this class to apply matching logic manually.
20 | *
21 | * @since 1.12.0
22 | * @author Ozren Dabić
23 | * @see Pattern
24 | * @see Query
25 | */
26 | @Getter
27 | @AllArgsConstructor(access = AccessLevel.PRIVATE)
28 | @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
29 | public class Predicate {
30 |
31 | Pattern pattern;
32 | List steps;
33 |
34 | @SuppressWarnings("unused")
35 | Predicate(Pattern pattern, Step[] steps) {
36 | this(pattern, List.of(steps));
37 | }
38 |
39 | @Override
40 | public String toString() {
41 | StringBuilder builder = new StringBuilder();
42 | for (int i = 0; i < steps.size() - 1; i++) {
43 | Step step = steps.get(i);
44 | String value = step.getValue();
45 | if (i == 0) {
46 | builder.append(value);
47 | continue;
48 | }
49 | builder.append(" ");
50 | switch (step.getType()) {
51 | case CAPTURE:
52 | builder.append("@").append(value);
53 | break;
54 | case STRING:
55 | builder.append('"').append(value).append('"');
56 | break;
57 | default:
58 | }
59 | }
60 |
61 | return "(#" + builder + ")";
62 | }
63 |
64 | /**
65 | * Represents a single step in a {@link Predicate}.
66 | * Each step is characterized by a {@link Type Type},
67 | * and an optional value.
68 | *
69 | * @since 1.12.0
70 | * @author Ozren Dabić
71 | */
72 | @Getter
73 | @AllArgsConstructor(access = AccessLevel.PRIVATE)
74 | @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
75 | public static class Step {
76 |
77 | Type type;
78 | String value;
79 |
80 | public Step(int type, String value) {
81 | this(Type.get(type), value);
82 | }
83 |
84 | /**
85 | * Represents the type of {@link Step Step}.
86 | *
87 | * @since 1.12.0
88 | * @author Ozren Dabić
89 | */
90 | public enum Type {
91 |
92 | /**
93 | * Steps with this type are sentinels that
94 | * represent the end of an individual predicate.
95 | * Only one such step is allowed per predicate.
96 | */
97 | DONE,
98 |
99 | /**
100 | * Steps with this type represent names of captures.
101 | */
102 | CAPTURE,
103 |
104 | /**
105 | * Steps with this type represent literal strings.
106 | */
107 | STRING;
108 |
109 | private static final Type[] VALUES = values();
110 |
111 | private static Type get(int ordinal) {
112 | return VALUES[ordinal];
113 | }
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/Quantifier.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | /**
4 | * Represents the {@link Capture} quantifier in a {@link Pattern},
5 | * i.e. the number of nodes that a capture should contain. Within a
6 | * query, a capture can have different quantifiers for each pattern.
7 | *
8 | * @since 1.12.0
9 | * @author Ozren Dabić
10 | */
11 | public enum Quantifier {
12 |
13 | /**
14 | * The capture will not match any nodes,
15 | * as said capture is not present in a
16 | * specific pattern.
17 | */
18 | ZERO,
19 | /**
20 | * The capture will match at most one node.
21 | * Example:
22 | * {@code
23 | * ((_)? @capture)
24 | * }
25 | */
26 | ZERO_OR_ONE,
27 | /**
28 | * The capture will match any number of nodes.
29 | * Example:
30 | * {@code
31 | * ((_)* @capture)
32 | * }
33 | */
34 | ZERO_OR_MORE,
35 | /**
36 | * The capture will match exactly one node.
37 | * Example:
38 | * {@code
39 | * ((_) @capture)
40 | * }
41 | */
42 | ONE,
43 | /**
44 | * The capture will match at least one node.
45 | * Example:
46 | * {@code
47 | * ((_)+ @capture)
48 | * }
49 | */
50 | ONE_OR_MORE
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/QueryCursor.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.Generated;
5 | import lombok.Getter;
6 | import lombok.experimental.FieldDefaults;
7 | import lombok.experimental.NonFinal;
8 | import org.jetbrains.annotations.NotNull;
9 |
10 | import java.util.Iterator;
11 | import java.util.NoSuchElementException;
12 |
13 | /**
14 | * Cursor used for executing queries, carrying the state needed to process them.
15 | *
16 | * The query cursor should not be shared between threads, but can be reused for many query executions.
17 | *
18 | * @since 1.0.0
19 | * @author Ozren Dabić
20 | */
21 | @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
22 | public class QueryCursor extends External implements Iterable {
23 |
24 | Node node;
25 | Query query;
26 |
27 | @Getter
28 | @NonFinal
29 | boolean executed = false;
30 |
31 | @SuppressWarnings("unused")
32 | QueryCursor(long pointer, @NotNull Node node, @NotNull Query query) {
33 | super(pointer);
34 | this.node = node;
35 | this.query = query;
36 | }
37 |
38 | @Override
39 | protected native void delete();
40 |
41 | /**
42 | * Start running a query against a node.
43 | */
44 | public native void execute();
45 |
46 | /**
47 | * Set the range of bytes positions in which the query will be executed.
48 | *
49 | * @param startByte The start byte of the query range
50 | * @param endByte The end byte of the query range
51 | * @throws ch.usi.si.seart.treesitter.exception.ByteOffsetOutOfBoundsException
52 | * if either argument is outside the queried node's byte range
53 | * @throws IllegalArgumentException if:
54 | *
55 | * - {@code startByte} < 0
56 | * - {@code endByte} < 0
57 | * - {@code startByte} > {@code endByte}
58 | *
59 | * @since 1.9.0
60 | */
61 | public native void setRange(int startByte, int endByte);
62 |
63 | /**
64 | * Set the range of row-column coordinates in which the query will be executed.
65 | *
66 | * @param startPoint The start point of the query range
67 | * @param endPoint The end point of the query range
68 | * @throws NullPointerException if either argument is null
69 | * @throws IllegalArgumentException if any point coordinates are negative,
70 | * or if {@code startPoint} is a position that comes after {@code endPoint}
71 | * @throws ch.usi.si.seart.treesitter.exception.PointOutOfBoundsException
72 | * if any of the arguments is outside the queried node's position range
73 | * @since 1.9.0
74 | */
75 | public native void setRange(@NotNull Point startPoint, @NotNull Point endPoint);
76 |
77 | /**
78 | * Advance to the next match of the currently running query.
79 | *
80 | * @return A match if there is one, null otherwise
81 | * @throws IllegalStateException if the query was not executed beforehand
82 | * @see #execute()
83 | */
84 | public native QueryMatch nextMatch();
85 |
86 | /**
87 | * Returns an iterator over the query matches,
88 | * starting from the first {@link QueryMatch}.
89 | * Implicitly calls {@link #execute()}.
90 | *
91 | * @return an iterator over query cursor matches
92 | */
93 | @Override
94 | public @NotNull Iterator iterator() {
95 | execute();
96 | return new Iterator<>() {
97 |
98 | private QueryMatch current = nextMatch();
99 |
100 | @Override
101 | public boolean hasNext() {
102 | return current != null;
103 | }
104 |
105 | @Override
106 | public QueryMatch next() {
107 | if (!hasNext()) throw new NoSuchElementException();
108 | QueryMatch match = current;
109 | current = nextMatch();
110 | return match;
111 | }
112 | };
113 | }
114 |
115 | @Override
116 | @Generated
117 | public String toString() {
118 | return String.format("QueryCursor(node: %s, query: %s)", node, query);
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/QueryMatch.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.Generated;
5 | import lombok.Getter;
6 | import lombok.experimental.FieldDefaults;
7 | import org.apache.commons.collections4.MultiValuedMap;
8 | import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
9 | import org.apache.commons.collections4.multimap.UnmodifiableMultiValuedMap;
10 | import org.jetbrains.annotations.NotNull;
11 |
12 | import java.util.Collection;
13 | import java.util.Map;
14 | import java.util.stream.Collectors;
15 | import java.util.stream.Stream;
16 |
17 | /**
18 | * Represents a collection of captured nodes, matched with a single query {@link Pattern}.
19 | *
20 | * @since 1.0.0
21 | * @author Ozren Dabić
22 | */
23 | @Getter
24 | @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
25 | public class QueryMatch {
26 |
27 | int id;
28 | Pattern pattern;
29 | MultiValuedMap captures;
30 |
31 | @SuppressWarnings("unused")
32 | QueryMatch(int id, @NotNull Pattern pattern, @NotNull Map.Entry[] captures) {
33 | this.id = id;
34 | this.pattern = pattern;
35 | this.captures = UnmodifiableMultiValuedMap.unmodifiableMultiValuedMap(
36 | Stream.of(captures).collect(
37 | ArrayListValuedHashMap::new,
38 | (map, entry) -> map.put(entry.getKey(), entry.getValue()),
39 | MultiValuedMap::putAll
40 | )
41 | );
42 | }
43 |
44 | /**
45 | * Retrieves a mapping between the captures and captured nodes in this match.
46 | *
47 | * @return a map of captures and the nodes they captured
48 | * @since 1.7.0
49 | */
50 | public Map> getCaptures() {
51 | return captures.asMap();
52 | }
53 |
54 | /**
55 | * Retrieves all the captured nodes from this match.
56 | *
57 | * @return a collection of captured nodes
58 | * @since 1.7.0
59 | */
60 | public Collection getNodes() {
61 | return captures.values();
62 | }
63 |
64 | /**
65 | * Retrieves all nodes captured under a specific capture.
66 | *
67 | * @param capture the query capture
68 | * @return a collection of captured nodes
69 | * @since 1.7.0
70 | */
71 | public Collection getNodes(Capture capture) {
72 | return captures.get(capture);
73 | }
74 |
75 | /**
76 | * Retrieves all nodes captured under a specific capture with the given name.
77 | *
78 | * @param name the name of the query capture
79 | * @return a collection of captured nodes
80 | * @since 1.7.0
81 | */
82 | public Collection getNodes(String name) {
83 | return captures.entries().stream()
84 | .filter(entry -> {
85 | Capture capture = entry.getKey();
86 | return name.equals(capture.getName());
87 | })
88 | .map(Map.Entry::getValue)
89 | .collect(Collectors.toUnmodifiableList());
90 | }
91 |
92 | @Override
93 | @Generated
94 | public String toString() {
95 | String joined = captures.entries().stream()
96 | .map(entry -> entry.getKey() + "=" + entry.getValue())
97 | .collect(Collectors.joining(", ", "{", "}"));
98 | return String.format("QueryMatch(id: %d, pattern: '%s', captures: [%s])", id, pattern, joined);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/Range.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.AllArgsConstructor;
5 | import lombok.EqualsAndHashCode;
6 | import lombok.Generated;
7 | import lombok.Getter;
8 | import lombok.NoArgsConstructor;
9 | import lombok.experimental.FieldDefaults;
10 | import org.jetbrains.annotations.NotNull;
11 |
12 | import java.util.Objects;
13 |
14 | /**
15 | * Represents the portions of source code taken up by a node within a file or snippet.
16 | * Each range consists of a:
17 | *
18 | * - start byte offset
19 | * - end byte offset
20 | * - start point
21 | * - end point
22 | *
23 | *
24 | * @since 1.0.0
25 | * @author Ozren Dabić
26 | */
27 | @Getter
28 | @EqualsAndHashCode
29 | @AllArgsConstructor(access = AccessLevel.PROTECTED)
30 | @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
31 | public class Range {
32 |
33 | int startByte;
34 | int endByte;
35 | Point startPoint;
36 | Point endPoint;
37 |
38 | /**
39 | * Obtain a new {@link Builder} instance for constructing a range.
40 | *
41 | * @return a new range builder
42 | */
43 | public static Builder builder() {
44 | return new Builder();
45 | }
46 |
47 | /**
48 | * Obtain a new {@link Builder} initialized with the current range's attributes.
49 | *
50 | * @return a new range builder
51 | * @since 1.12.0
52 | */
53 | public Builder toBuilder() {
54 | return builder().startByte(startByte)
55 | .endByte(endByte)
56 | .startPoint(startPoint)
57 | .endPoint(endPoint);
58 | }
59 |
60 | /**
61 | * Facilitates the construction of {@link Range} instances.
62 | * It allows for the step-by-step creation of these objects
63 | * by providing methods for setting individual attributes.
64 | * Input validations are performed at each build step.
65 | *
66 | * @since 1.12.0
67 | */
68 | @FieldDefaults(level = AccessLevel.PRIVATE)
69 | @NoArgsConstructor(access = AccessLevel.PRIVATE)
70 | public static final class Builder {
71 |
72 | int startByte = 0;
73 | int endByte = Integer.MAX_VALUE;
74 | Point startPoint = Point.ORIGIN();
75 | Point endPoint = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE);
76 |
77 | /**
78 | * Sets the start byte offset for this range.
79 | *
80 | * @param value byte value offset
81 | * @return this builder
82 | * @throws IllegalArgumentException if the value is negative
83 | */
84 | public Builder startByte(int value) {
85 | if (value < 0) throw new IllegalArgumentException("Start byte cannot be negative");
86 | startByte = value;
87 | return this;
88 | }
89 |
90 | /**
91 | * Sets the end byte offset for this range.
92 | *
93 | * @param value byte value offset
94 | * @return this builder
95 | * @throws IllegalArgumentException if the value is negative
96 | */
97 | public Builder endByte(int value) {
98 | if (value < 0) throw new IllegalArgumentException("End byte cannot be negative");
99 | endByte = value;
100 | return this;
101 | }
102 |
103 | /**
104 | * Sets the start point for this range.
105 | *
106 | * @param point the point
107 | * @return this builder
108 | * @throws NullPointerException if the point is {@code null}
109 | * @throws IllegalArgumentException if the point has negative coordinates
110 | */
111 | public Builder startPoint(@NotNull Point point) {
112 | Objects.requireNonNull(point, "Start point cannot be null");
113 | if (point.getRow() < 0 || point.getColumn() < 0)
114 | throw new IllegalArgumentException("Start point cannot have negative coordinates");
115 | startPoint = point;
116 | return this;
117 | }
118 |
119 | /**
120 | * Sets the end point for this range.
121 | *
122 | * @param point the point
123 | * @return this builder
124 | * @throws NullPointerException if the point is {@code null}
125 | * @throws IllegalArgumentException if the point has negative coordinates
126 | */
127 | public Builder endPoint(@NotNull Point point) {
128 | Objects.requireNonNull(point, "End point cannot be null");
129 | if (point.getRow() < 0 || point.getColumn() < 0)
130 | throw new IllegalArgumentException("End point cannot have negative coordinates");
131 | endPoint = point;
132 | return this;
133 | }
134 |
135 | /**
136 | * Builds a new range instance with the
137 | * attributes specified in this builder.
138 | *
139 | * @return a new range instance
140 | * @throws IllegalArgumentException
141 | * if the start byte is greater than the end byte,
142 | * or if the start point is greater than the end point
143 | * @see Point#compareTo(Point)
144 | */
145 | public Range build() {
146 | if (Integer.compareUnsigned(startByte, endByte) > 0)
147 | throw new IllegalArgumentException("Start byte cannot be greater than end byte");
148 | if (startPoint.compareTo(endPoint) > 0)
149 | throw new IllegalArgumentException("Start point cannot be greater than end point");
150 | return new Range(startByte, endByte, startPoint, endPoint);
151 | }
152 | }
153 |
154 | Range(@NotNull Node node) {
155 | this(node.getStartByte(), node.getEndByte(), node.getStartPoint(), node.getEndPoint());
156 | }
157 |
158 | /**
159 | * Returns a string representation of this range in the format:
160 | * {@code
161 | * [startPoint] - [endPoint]
162 | * }
163 | *
164 | * @return A string representation of this range
165 | */
166 | @Override
167 | @Generated
168 | public String toString() {
169 | return String.format("[%s] - [%s]", startPoint, endPoint);
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/Symbol.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.AllArgsConstructor;
5 | import lombok.Generated;
6 | import lombok.Getter;
7 | import lombok.experimental.FieldDefaults;
8 |
9 | import java.util.Objects;
10 |
11 | /**
12 | * Represents a symbol in an abstract syntax {@link Tree}.
13 | * Symbols are used to identify nodes in the AST.
14 | * Each symbol has an associated ID, {@link Type Type}, and name.
15 | *
16 | * @author Ozren Dabić
17 | * @since 1.6.0
18 | */
19 | @Getter
20 | @AllArgsConstructor(access = AccessLevel.PACKAGE)
21 | @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
22 | public class Symbol {
23 |
24 | int id;
25 | Type type;
26 | String name;
27 |
28 | @SuppressWarnings("unused")
29 | Symbol(int id, int ordinal, String name) {
30 | this(id, Type.get(ordinal), name);
31 | }
32 |
33 | @Override
34 | @Generated
35 | public String toString() {
36 | return String.format("Symbol(id: %d, type: %s, name: '%s')", id, type, name);
37 | }
38 |
39 | @Override
40 | @Generated
41 | public boolean equals(Object obj) {
42 | if (this == obj) return true;
43 | if (obj == null || getClass() != obj.getClass()) return false;
44 | Symbol symbol = (Symbol) obj;
45 | return id == symbol.id && type == symbol.type && Objects.equals(name, symbol.name);
46 | }
47 |
48 | @Override
49 | @Generated
50 | public int hashCode() {
51 | return Objects.hash(id, type, name);
52 | }
53 |
54 | /**
55 | * Enumeration representing the possible types of symbols.
56 | * This includes:
57 | *
58 | * - Named nodes ({@link #REGULAR})
59 | * - Anonymous nodes ({@link #ANONYMOUS})
60 | * - Hidden nodes ({@link #AUXILIARY})
61 | *
62 | *
63 | * @author Ozren Dabić
64 | * @since 1.6.0
65 | */
66 | public enum Type {
67 |
68 | REGULAR,
69 | ANONYMOUS,
70 | AUXILIARY;
71 |
72 | private static final Type[] VALUES = values();
73 |
74 | private static Type get(int ordinal) {
75 | return VALUES[ordinal];
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/Tree.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.Getter;
5 | import lombok.experimental.FieldDefaults;
6 | import org.jetbrains.annotations.NotNull;
7 |
8 | import java.nio.charset.Charset;
9 | import java.nio.charset.StandardCharsets;
10 | import java.util.Arrays;
11 | import java.util.Iterator;
12 | import java.util.List;
13 |
14 | /**
15 | * A Tree represents the syntax tree of an entire source code file.
16 | * It contains {@link Node} instances that indicate the structure of the source code.
17 | *
18 | * @since 1.0.0
19 | * @author Tommy MacWilliam
20 | * @author Ozren Dabić
21 | */
22 | @Getter
23 | @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
24 | public class Tree extends External implements Iterable, Cloneable {
25 |
26 | private static final Charset CHARSET = StandardCharsets.UTF_16LE;
27 |
28 | Language language;
29 | String source;
30 |
31 | Tree(long pointer, @NotNull Language language, @NotNull String source) {
32 | super(pointer);
33 | this.language = language;
34 | this.source = source;
35 | }
36 |
37 | @Override
38 | protected native void delete();
39 |
40 | /**
41 | * Edit the syntax tree to keep it in sync with source code that has been edited.
42 | *
43 | * @param edit changes made to the source code in terms of
44 | * both byte offsets and row/column coordinates
45 | */
46 | public native void edit(@NotNull InputEdit edit);
47 |
48 | /**
49 | * Compare this old edited syntax tree to a new syntax tree representing the same document,
50 | * returning a sequence of {@link Range} instances, their coordinates corresponding to changes
51 | * made to the syntactic structure.
52 | *
53 | * For this to work correctly, this syntax tree must have been edited such that its
54 | * ranges match up to the new tree. Generally, you'll want to call this method right
55 | * after calling one of the {@link Parser} methods.
56 | *
57 | * @param other the tree to compare with
58 | * @return a list of ranges that have been changed
59 | * @throws NullPointerException if {@code other} is null
60 | * @since 1.12.0
61 | * @see Parser#parse(String, Tree)
62 | * @see Parser#parse(java.nio.file.Path, Tree) Parser.parse(Path, Tree)
63 | */
64 | public native List getChangedRanges(@NotNull Tree other);
65 |
66 | /**
67 | * Get the topmost {@link Node} of the syntax tree.
68 | *
69 | * @return the root node of the syntax tree
70 | */
71 | public native Node getRootNode();
72 |
73 | /**
74 | * Returns an iterator over the entire syntax tree, starting from the root.
75 | *
76 | * @return an iterator over syntax tree nodes
77 | * @see Node#iterator()
78 | */
79 | @Override
80 | public @NotNull Iterator iterator() {
81 | return getRootNode().iterator();
82 | }
83 |
84 | /**
85 | * Clone this tree, creating a separate, independent instance.
86 | *
87 | * @return a clone of this instance
88 | * @since 1.6.0
89 | */
90 | @Override
91 | public native Tree clone();
92 |
93 | String getSource(int startByte, int endByte) {
94 | byte[] bytes = source.getBytes(CHARSET);
95 | byte[] copy = Arrays.copyOfRange(bytes, startByte * 2, endByte * 2);
96 | return new String(copy, CHARSET);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/TreeCursorNode.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.AllArgsConstructor;
5 | import lombok.Generated;
6 | import lombok.Getter;
7 | import lombok.experimental.FieldDefaults;
8 |
9 | /**
10 | * Special type of node returned during tree traversals with {@link TreeCursor}.
11 | *
12 | * @since 1.0.0
13 | * @author Tommy MacWilliam
14 | * @author Ozren Dabić
15 | */
16 | @Getter
17 | @AllArgsConstructor(access = AccessLevel.PACKAGE)
18 | @FieldDefaults(level = AccessLevel.PRIVATE)
19 | public class TreeCursorNode {
20 |
21 | String name;
22 | String type;
23 | String content;
24 | int startByte;
25 | int endByte;
26 | Point startPoint;
27 | Point endPoint;
28 | boolean isNamed;
29 |
30 | @SuppressWarnings("unused")
31 | TreeCursorNode(String name, Node node) {
32 | this(
33 | name,
34 | node.getType(),
35 | node.getContent(),
36 | node.getStartByte(),
37 | node.getEndByte(),
38 | node.getStartPoint(),
39 | node.getEndPoint(),
40 | node.isNamed()
41 | );
42 | }
43 |
44 | @Override
45 | @Generated
46 | public String toString() {
47 | String field = name != null ? name + ": " : "";
48 | return String.format("%s%s [%s] - [%s]", field, type, startPoint, endPoint);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/error/ABIVersionError.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.error;
2 |
3 | import lombok.experimental.StandardException;
4 |
5 | /**
6 | * An error raised whenever there is an ABI version mismatch.
7 | *
8 | * These usually occur as a result of a language being generated
9 | * with an incompatible version of the Tree-sitter CLI.
10 | *
11 | * @since 1.0.0
12 | * @author Ozren Dabić
13 | */
14 | @StandardException
15 | public class ABIVersionError extends LinkageError {
16 |
17 | private static final String TEMPLATE = "Incompatible language version: %d";
18 |
19 | public ABIVersionError(int version) {
20 | super(String.format(TEMPLATE, version));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/error/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Provides {@link java.lang.Error} subclasses related to tree-sitter.
3 | *
4 | * @since 1.0.0
5 | */
6 | package ch.usi.si.seart.treesitter.error;
7 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/exception/ByteOffsetOutOfBoundsException.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.exception;
2 |
3 | /**
4 | * Thrown to indicate that a specified byte offset is outside a node's byte range.
5 | *
6 | * @since 1.7.0
7 | * @author Ozren Dabić
8 | */
9 | public class ByteOffsetOutOfBoundsException extends NodeRangeBoundaryException {
10 |
11 | public ByteOffsetOutOfBoundsException(int offset) {
12 | super("Byte offset outside node range: " + offset);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/exception/NodeRangeBoundaryException.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.exception;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.experimental.StandardException;
5 |
6 | /**
7 | * The base exception type for all exceptions involving invalid node positional offsets.
8 | *
9 | * @since 1.7.0
10 | * @author Ozren Dabić
11 | */
12 | @StandardException(access = AccessLevel.PROTECTED)
13 | abstract class NodeRangeBoundaryException extends TreeSitterException {
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/exception/PointOutOfBoundsException.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.exception;
2 |
3 | import ch.usi.si.seart.treesitter.Point;
4 |
5 | /**
6 | * Thrown to indicate that a specified point is outside a node's point range.
7 | *
8 | * @since 1.7.0
9 | * @author Ozren Dabić
10 | */
11 | public class PointOutOfBoundsException extends NodeRangeBoundaryException {
12 |
13 | public PointOutOfBoundsException(Point point) {
14 | super("Point outside node range: " + point);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/exception/TreeSitterException.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.exception;
2 |
3 | import lombok.experimental.StandardException;
4 |
5 | /**
6 | * The base exception type for all tree-sitter exceptions.
7 | *
8 | * @since 1.0.0
9 | * @author Ozren Dabić
10 | */
11 | @StandardException
12 | public class TreeSitterException extends RuntimeException {
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/exception/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Provides {@link java.lang.Exception} subclasses related to tree-sitter.
3 | *
4 | * @since 1.0.0
5 | */
6 | package ch.usi.si.seart.treesitter.exception;
7 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/exception/parser/IncompatibleLanguageException.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.exception.parser;
2 |
3 | import ch.usi.si.seart.treesitter.Language;
4 | import lombok.experimental.StandardException;
5 |
6 | /**
7 | * Thrown when attempts to set the parser language result in failure.
8 | *
9 | * @since 1.6.0
10 | * @author Ozren Dabić
11 | */
12 | @StandardException
13 | public class IncompatibleLanguageException extends ParserException {
14 |
15 | private static final String TEMPLATE = "Could not assign language to parser: %s";
16 |
17 | @SuppressWarnings("unused")
18 | public IncompatibleLanguageException(Language language) {
19 | super(String.format(TEMPLATE, language));
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/exception/parser/ParserException.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.exception.parser;
2 |
3 | import ch.usi.si.seart.treesitter.exception.TreeSitterException;
4 | import lombok.AccessLevel;
5 | import lombok.experimental.StandardException;
6 |
7 | /**
8 | * The base exception type for all exceptions related to tree-sitter parsers.
9 | *
10 | * @since 1.6.0
11 | * @author Ozren Dabić
12 | */
13 | @StandardException(access = AccessLevel.PROTECTED)
14 | public abstract class ParserException extends TreeSitterException {
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/exception/parser/ParsingException.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.exception.parser;
2 |
3 | import lombok.experimental.StandardException;
4 |
5 | /**
6 | * Thrown when there is an error during parsing.
7 | *
8 | * @since 1.6.0
9 | * @author Ozren Dabić
10 | */
11 | @StandardException
12 | public class ParsingException extends ParserException {
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/exception/parser/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Provides parser-specific exception class hierarchy.
3 | *
4 | * @since 1.6.0
5 | */
6 | package ch.usi.si.seart.treesitter.exception.parser;
7 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/exception/query/QueryCaptureException.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.exception.query;
2 |
3 | import lombok.experimental.StandardException;
4 |
5 | /**
6 | * Thrown when a query string has incorrect captures.
7 | *
8 | * @since 1.0.0
9 | * @author Ozren Dabić
10 | */
11 | @SuppressWarnings("unused")
12 | @StandardException
13 | public class QueryCaptureException extends QueryException {
14 |
15 | public QueryCaptureException(int offset) {
16 | super("Bad capture at offset " + offset);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/exception/query/QueryException.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.exception.query;
2 |
3 | import ch.usi.si.seart.treesitter.exception.TreeSitterException;
4 | import lombok.experimental.StandardException;
5 |
6 | /**
7 | * The base exception type for all exceptions related to tree-sitter queries.
8 | *
9 | * @since 1.0.0
10 | * @author Ozren Dabić
11 | */
12 | @StandardException
13 | public abstract class QueryException extends TreeSitterException {
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/exception/query/QueryFieldException.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.exception.query;
2 |
3 | import lombok.experimental.StandardException;
4 |
5 | /**
6 | * Thrown when the targeted node does not have a field with the specified name.
7 | *
8 | * @since 1.0.0
9 | * @author Ozren Dabić
10 | */
11 | @SuppressWarnings("unused")
12 | @StandardException
13 | public class QueryFieldException extends QueryException {
14 |
15 | public QueryFieldException(int offset) {
16 | super("Bad field name at offset " + offset);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/exception/query/QueryNodeTypeException.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.exception.query;
2 |
3 | import lombok.experimental.StandardException;
4 |
5 | /**
6 | * Thrown when a specified node type does not exist in the language grammar.
7 | *
8 | * @since 1.0.0
9 | * @author Ozren Dabić
10 | */
11 | @SuppressWarnings("unused")
12 | @StandardException
13 | public class QueryNodeTypeException extends QueryException {
14 |
15 | public QueryNodeTypeException(int offset) {
16 | super("Bad node name at offset " + offset);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/exception/query/QueryStructureException.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.exception.query;
2 |
3 | import lombok.experimental.StandardException;
4 |
5 | /**
6 | * Thrown when the node structure in the query does not adhere to the grammar.
7 | *
8 | * @since 1.0.0
9 | * @author Ozren Dabić
10 | */
11 | @SuppressWarnings("unused")
12 | @StandardException
13 | public class QueryStructureException extends QueryException {
14 |
15 | public QueryStructureException(int offset) {
16 | super("Bad pattern structure at offset " + offset);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/exception/query/QuerySyntaxException.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.exception.query;
2 |
3 | import lombok.experimental.StandardException;
4 |
5 | /**
6 | * Thrown when a query has incorrect syntax.
7 | *
8 | * @since 1.0.0
9 | * @author Ozren Dabić
10 | */
11 | @SuppressWarnings("unused")
12 | @StandardException
13 | public class QuerySyntaxException extends QueryException {
14 |
15 | public QuerySyntaxException(int offset) {
16 | super("Bad syntax at offset " + offset);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/exception/query/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Provides query-specific exception class hierarchy.
3 | *
4 | * @since 1.0.0
5 | */
6 | package ch.usi.si.seart.treesitter.exception.query;
7 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/function/IOExceptionThrowingConsumer.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.function;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 |
5 | import java.io.IOException;
6 | import java.io.UncheckedIOException;
7 | import java.util.Objects;
8 | import java.util.function.Consumer;
9 |
10 | /**
11 | * Represents an operation that accepts a single input argument and returns no result.
12 | * Can potentially throw an {@link IOException} while doing so.
13 | *
14 | * @since 1.2.0
15 | * @see java.util.function.Consumer Consumer
16 | * @param the type of the input to the operation
17 | * @author Ozren Dabić
18 | */
19 | @FunctionalInterface
20 | public interface IOExceptionThrowingConsumer {
21 |
22 | /**
23 | * Performs this operation on the given argument.
24 | *
25 | * @param t the input argument
26 | * @throws IOException if an I/O error occurs
27 | */
28 | void accept(T t) throws IOException;
29 |
30 | /**
31 | * Returns a wrapped {@code Consumer} that performs this operation.
32 | * If performing said operation results in an {@code IOException},
33 | * it is re-thrown as its unchecked counterpart: {@code UncheckedIOException}.
34 | *
35 | * @param consumer the operation to wrap
36 | * @param the type of the input to the operation
37 | * @return and identical {@code Consumer} that throws
38 | * {@code UncheckedIOException} instead
39 | * @throws NullPointerException if {@code consumer} is null
40 | */
41 | static Consumer toUnchecked(@NotNull IOExceptionThrowingConsumer consumer) {
42 | Objects.requireNonNull(consumer, "Throwing consumer must not be null!");
43 | return t -> {
44 | try {
45 | consumer.accept(t);
46 | } catch (IOException ex) {
47 | throw new UncheckedIOException(ex);
48 | }
49 | };
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/function/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Provides functional interface utilities.
3 | *
4 | * @since 1.2.0
5 | */
6 | package ch.usi.si.seart.treesitter.function;
7 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Provides all the core classes for interfacing with the tree-sitter API.
3 | *
4 | * @since 1.0.0
5 | */
6 | package ch.usi.si.seart.treesitter;
7 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/printer/DotGraphPrinter.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.printer;
2 |
3 | import ch.usi.si.seart.treesitter.Tree;
4 | import lombok.AccessLevel;
5 | import lombok.experimental.FieldDefaults;
6 | import org.jetbrains.annotations.NotNull;
7 |
8 | import java.io.File;
9 | import java.io.IOException;
10 | import java.io.UncheckedIOException;
11 | import java.nio.file.Files;
12 | import java.nio.file.Path;
13 | import java.util.Objects;
14 |
15 | /**
16 | * Printer used for generating DOT graph representations of trees.
17 | * Unlike its sister classes, it does not rely on an iterative approach,
18 | * relying instead on the internal {@code tree-sitter} API.
19 | *
20 | * @since 1.2.0
21 | * @author Ozren Dabić
22 | */
23 | @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
24 | public class DotGraphPrinter implements TreePrinter {
25 |
26 | @SuppressWarnings({"FieldCanBeLocal", "unused"})
27 | Tree tree;
28 |
29 | public DotGraphPrinter(@NotNull Tree tree) {
30 | this.tree = Objects.requireNonNull(tree, "Tree must not be null!");
31 | }
32 |
33 | /**
34 | * Generates a DOT graph of the tree.
35 | *
36 | * @return A DOT graph string of the tree
37 | */
38 | @Override
39 | public String print() {
40 | try {
41 | File file = export();
42 | Path path = file.toPath();
43 | String contents = Files.readString(path);
44 | Files.delete(path);
45 | return contents;
46 | } catch (IOException ex) {
47 | throw new UncheckedIOException(ex);
48 | }
49 | }
50 |
51 | /**
52 | * Generates a DOT graph of the tree,
53 | * writing it directly to a file.
54 | *
55 | * @return A file containing the DOT graph of the tree
56 | * @throws IOException if an I/O error occurs
57 | */
58 | @Override
59 | public File export() throws IOException {
60 | File file = Files.createTempFile("ts-export-", ".dot").toFile();
61 | write(file);
62 | return file;
63 | }
64 |
65 | private native void write(File file) throws IOException;
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/printer/IterativeTreePrinter.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.printer;
2 |
3 | import ch.usi.si.seart.treesitter.TreeCursor;
4 | import ch.usi.si.seart.treesitter.function.IOExceptionThrowingConsumer;
5 | import lombok.AccessLevel;
6 | import lombok.experimental.FieldDefaults;
7 | import org.jetbrains.annotations.NotNull;
8 |
9 | import java.io.BufferedWriter;
10 | import java.io.File;
11 | import java.io.FileWriter;
12 | import java.io.IOException;
13 | import java.io.UncheckedIOException;
14 | import java.io.Writer;
15 | import java.nio.file.Files;
16 | import java.util.Objects;
17 | import java.util.function.Consumer;
18 |
19 | @FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true)
20 | abstract class IterativeTreePrinter implements TreePrinter {
21 |
22 | TreeCursor cursor;
23 |
24 | protected IterativeTreePrinter(@NotNull TreeCursor cursor) {
25 | this.cursor = Objects.requireNonNull(cursor, "Cursor must not be null!");
26 | }
27 |
28 | /**
29 | * Generates a string representation of the Abstract Syntax Tree (AST),
30 | * starting from the node currently pointed to by the cursor.
31 | *
32 | * @return A string representation of the tree
33 | */
34 | public final String print() {
35 | StringBuilder stringBuilder = new StringBuilder(getPreamble());
36 | write(stringBuilder::append);
37 | return stringBuilder.toString();
38 | }
39 |
40 | /**
41 | * Generates a string representation of the Abstract Syntax Tree (AST),
42 | * starting from the node currently pointed to by the cursor,
43 | * while writing outputs directly to a file.
44 | *
45 | * @return A file containing the string of the tree
46 | * @throws IOException if an I/O error occurs
47 | */
48 | public final File export() throws IOException {
49 | File file = Files.createTempFile("ts-export-", getFileExtension()).toFile();
50 | try (Writer writer = new BufferedWriter(new FileWriter(file))) {
51 | writer.append(getPreamble());
52 | Consumer appender = IOExceptionThrowingConsumer.toUnchecked(writer::append);
53 | write(appender);
54 | return file;
55 | } catch (UncheckedIOException ex) {
56 | throw ex.getCause();
57 | }
58 | }
59 |
60 | protected String getPreamble() {
61 | return "";
62 | }
63 |
64 | protected String getFileExtension() {
65 | return "";
66 | }
67 |
68 | protected abstract void write(Consumer appender);
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/printer/SymbolicExpressionPrinter.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.printer;
2 |
3 | import ch.usi.si.seart.treesitter.TreeCursor;
4 | import ch.usi.si.seart.treesitter.TreeCursorNode;
5 | import lombok.AccessLevel;
6 | import lombok.experimental.FieldDefaults;
7 | import org.jetbrains.annotations.NotNull;
8 |
9 | import java.util.function.Consumer;
10 |
11 | /**
12 | * Printer used for generating Symbolic Expression (S-expression)
13 | * representations of trees using an iterative approach.
14 | * Note that unlike the CLI counterpart, the resulting symbolic
15 | * expression does not contain the positional information of the
16 | * node.
17 | *
18 | * @since 1.4.0
19 | * @see Rust implementation
20 | * @author Ozren Dabić
21 | */
22 | @FieldDefaults(level = AccessLevel.PRIVATE)
23 | public class SymbolicExpressionPrinter extends IterativeTreePrinter {
24 |
25 | public SymbolicExpressionPrinter(@NotNull TreeCursor cursor) {
26 | super(cursor);
27 | }
28 |
29 | @Override
30 | protected String getFileExtension() {
31 | return ".scm";
32 | }
33 |
34 | protected void write(Consumer appender) {
35 | boolean needsSpace = false;
36 | boolean visitedChildren = false;
37 | for (;;) {
38 | TreeCursorNode cursorNode = cursor.getCurrentTreeCursorNode();
39 | boolean isNamed = cursorNode.isNamed();
40 | String type = cursorNode.getType();
41 | String name = cursorNode.getName();
42 | if (visitedChildren) {
43 | if (isNamed) {
44 | appender.accept(")");
45 | needsSpace = true;
46 | }
47 | if (cursor.gotoNextSibling()) visitedChildren = false;
48 | else if (cursor.gotoParent()) visitedChildren = true;
49 | else return;
50 | } else {
51 | if (isNamed) {
52 | if (needsSpace)
53 | appender.accept(" ");
54 | if (name != null) {
55 | appender.accept(name);
56 | appender.accept(": ");
57 | }
58 | appender.accept("(");
59 | appender.accept(type);
60 | needsSpace = true;
61 | }
62 | visitedChildren = !cursor.gotoFirstChild();
63 | }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/printer/SyntaxTreePrinter.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.printer;
2 |
3 | import ch.usi.si.seart.treesitter.TreeCursor;
4 | import ch.usi.si.seart.treesitter.TreeCursorNode;
5 | import org.jetbrains.annotations.NotNull;
6 |
7 | import java.util.function.Consumer;
8 |
9 | /**
10 | * Printer used for generating human-readable
11 | * representations of trees using an iterative approach.
12 | * Each node of the subtree is represented as follows:
13 | * {@code
14 | * optional_field_name: node_name [start_column:start_row] - [end_column:end_row]
15 | * }
16 | * While indentation is used to represent the tree structure.
17 | *
18 | * @since 1.2.0
19 | * @see Syntax Tree Playground
20 | * @author Ozren Dabić
21 | */
22 | public class SyntaxTreePrinter extends IterativeTreePrinter {
23 |
24 | public SyntaxTreePrinter(@NotNull TreeCursor cursor) {
25 | super(cursor);
26 | }
27 |
28 | @Override
29 | protected String getFileExtension() {
30 | return ".txt";
31 | }
32 |
33 | protected void write(Consumer appender) {
34 | for (;;) {
35 | TreeCursorNode cursorNode = cursor.getCurrentTreeCursorNode();
36 | if (cursorNode.isNamed()) {
37 | int depth = cursor.getCurrentDepth();
38 | String indent = " ".repeat(depth);
39 | appender.accept(indent);
40 | appender.accept(cursorNode.toString());
41 | appender.accept("\n");
42 | }
43 | if (cursor.gotoFirstChild() || cursor.gotoNextSibling()) continue;
44 | do {
45 | if (!cursor.gotoParent()) return;
46 | } while (!cursor.gotoNextSibling());
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/printer/TreePrinter.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.printer;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 |
6 | /**
7 | * Contract for classes that can generate string
8 | * representations of Abstract Syntax Trees (ASTs).
9 | * Implementations of this interface are responsible
10 | * for traversing trees and producing a specific representation.
11 | *
12 | * @since 1.2.0
13 | * @author Ozren Dabić
14 | */
15 | public interface TreePrinter {
16 |
17 | /**
18 | * Generates a string representation of an Abstract Syntax Tree (AST).
19 | *
20 | * @return A string representation of the tree
21 | */
22 | String print();
23 |
24 | /**
25 | * Generates a file containing the string
26 | * representation of an Abstract Syntax Tree (AST).
27 | * This method should be preferred over {@link #print()}
28 | * when dealing with extremely wide, or deep trees.
29 | *
30 | * @return A file containing the string representation of the tree
31 | * @throws IOException if an I/O error occurs
32 | */
33 | File export() throws IOException;
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/printer/XMLPrinter.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.printer;
2 |
3 | import ch.usi.si.seart.treesitter.Point;
4 | import ch.usi.si.seart.treesitter.TreeCursor;
5 | import ch.usi.si.seart.treesitter.TreeCursorNode;
6 | import org.jetbrains.annotations.NotNull;
7 |
8 | import java.util.ArrayDeque;
9 | import java.util.Deque;
10 | import java.util.function.Consumer;
11 |
12 | /**
13 | * Printer used for generating Extensible Markup Language (XML)
14 | * representations of trees using an iterative approach.
15 | * Note that unlike the CLI counterpart, the resulting XML
16 | * document does not contain the actual source code contents.
17 | *
18 | * @since 1.2.0
19 | * @see Rust implementation
20 | * @author Ozren Dabić
21 | */
22 | public class XMLPrinter extends IterativeTreePrinter {
23 |
24 | public static final String PROLOG = "";
25 |
26 | public XMLPrinter(@NotNull TreeCursor cursor) {
27 | super(cursor);
28 | }
29 |
30 | @Override
31 | protected String getPreamble() {
32 | return PROLOG;
33 | }
34 |
35 | @Override
36 | protected String getFileExtension() {
37 | return ".xml";
38 | }
39 |
40 | protected void write(Consumer appender) {
41 | boolean visitedChildren = false;
42 | Deque tags = new ArrayDeque<>();
43 | for (;;) {
44 | TreeCursorNode cursorNode = cursor.getCurrentTreeCursorNode();
45 | boolean isNamed = cursorNode.isNamed();
46 | String type = cursorNode.getType();
47 | String name = cursorNode.getName();
48 | Point start = cursorNode.getStartPoint();
49 | Point end = cursorNode.getEndPoint();
50 | if (visitedChildren) {
51 | if (isNamed) {
52 | appender.accept("");
53 | appender.accept(tags.pop());
54 | appender.accept(">");
55 | }
56 | if (cursor.gotoNextSibling()) visitedChildren = false;
57 | else if (cursor.gotoParent()) visitedChildren = true;
58 | else return;
59 | } else {
60 | if (isNamed) {
61 | appender.accept("<");
62 | appender.accept(type);
63 | appender.accept(" ");
64 | if (name != null) {
65 | appender.accept("name=\"");
66 | appender.accept(name);
67 | appender.accept("\" ");
68 | }
69 | appender.accept("start=\"");
70 | appender.accept(start.toString());
71 | appender.accept("\" ");
72 | appender.accept("end=\"");
73 | appender.accept(end.toString());
74 | appender.accept("\">");
75 | tags.push(type);
76 | }
77 | visitedChildren = !cursor.gotoFirstChild();
78 | }
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/ch/usi/si/seart/treesitter/printer/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Provides parse tree serialization utilities.
3 | *
4 | * @since 1.2.0
5 | */
6 | package ch.usi.si.seart.treesitter.printer;
7 |
--------------------------------------------------------------------------------
/src/main/javaTemplates/ch/usi/si/seart/treesitter/version/Version.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.version;
2 |
3 | import lombok.experimental.UtilityClass;
4 |
5 | /**
6 | * Utility used for obtaining the current version of the {@code ${project.artifactId}} codebase.
7 | * Unlike attempting to access the "Implementation-Version" manifest attribute from the JAR file,
8 | * this utility will work even if the {@link ClassLoader} does not expose the manifest metadata,
9 | * or if the code is not packaged in a JAR file.
10 | *
11 | * @author Ozren Dabić
12 | * @since 1.11.0
13 | */
14 | @UtilityClass
15 | public final class Version {
16 |
17 | private static final String VALUE = "${project.version}";
18 |
19 | /**
20 | * Get the current version of {@code ${project.artifactId}}.
21 | *
22 | * @return the semantic version string
23 | */
24 | public String getVersion() {
25 | return VALUE;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/javaTemplates/ch/usi/si/seart/treesitter/version/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Provides version information for the {@code tree-sitter} API and this library.
3 | */
4 | package ch.usi.si.seart.treesitter.version;
5 |
--------------------------------------------------------------------------------
/src/main/resources/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/seart-group/java-tree-sitter/b06ef81c0233487be4c9ef51b65aaa07734e5567/src/main/resources/.keep
--------------------------------------------------------------------------------
/src/test/java/ch/usi/si/seart/treesitter/AbstractQueryTest.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import lombok.Cleanup;
4 | import org.junit.jupiter.api.Test;
5 |
6 | public abstract class AbstractQueryTest extends BaseTest {
7 |
8 | protected Language getLanguage() {
9 | return Language.PYTHON;
10 | }
11 |
12 | protected String getSource() {
13 | return
14 | "@pure\n" +
15 | "@property\n" +
16 | "def foo(x):\n" +
17 | " pass\n" +
18 | "\n" +
19 | "@pure\n" +
20 | "@property\n" +
21 | "def bar(x):\n" +
22 | " pass\n";
23 | }
24 |
25 | protected String getPattern() {
26 | return "(_ (decorator)* @additional (function_definition) @target)";
27 | }
28 |
29 | protected abstract void assertions(Node node, Query query);
30 |
31 | @Test
32 | void test() {
33 | String source = getSource();
34 | Language language = getLanguage();
35 | @Cleanup Query query = Query.getFor(language, getPattern());
36 | @Cleanup Parser parser = Parser.getFor(language);
37 | @Cleanup Tree tree = parser.parse(source);
38 | Node root = tree.getRootNode();
39 | assertions(root, query);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/test/java/ch/usi/si/seart/treesitter/BaseTest.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import org.mockito.Mockito;
4 |
5 | import java.util.List;
6 |
7 | public abstract class BaseTest {
8 |
9 | protected static final Language invalid;
10 |
11 | protected static final Node empty = new Node.Null();
12 | protected static final Node treeless = new Node(1, 1, 1, 1, 1L, null);
13 |
14 | protected static final Point _0_0_ = new Point(0, 0);
15 | protected static final Point _0_1_ = new Point(0, 1);
16 | protected static final Point _1_0_ = new Point(1, 0);
17 | protected static final Point _1_1_ = new Point(1, 1);
18 | protected static final Point _2_2_ = new Point(2, 2);
19 |
20 | static {
21 | LibraryLoader.load();
22 | invalid = Mockito.mock(Language.class);
23 |
24 | Mockito.when(invalid.name()).thenReturn("INVALID");
25 | Mockito.when(invalid.ordinal()).thenReturn(Language.values().length);
26 |
27 | Mockito.when(invalid.getId()).thenReturn(0L);
28 | Mockito.when(invalid.getVersion()).thenReturn(0);
29 | Mockito.when(invalid.getTotalStates()).thenReturn(0);
30 | Mockito.when(invalid.getSymbols()).thenReturn(List.of());
31 | Mockito.when(invalid.getFields()).thenReturn(List.of());
32 | Mockito.when(invalid.getExtensions()).thenReturn(List.of());
33 |
34 | Mockito.when(invalid.nextState(Mockito.any())).thenCallRealMethod();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/test/java/ch/usi/si/seart/treesitter/CaptureTest.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import org.junit.jupiter.api.Assertions;
4 |
5 | import java.util.List;
6 |
7 | class CaptureTest extends AbstractQueryTest {
8 |
9 | @Override
10 | protected void assertions(Node node, Query query) {
11 | List captures = query.getCaptures();
12 | Capture additional = captures.get(0);
13 | Assertions.assertTrue(additional.isEnabled());
14 | additional.disable();
15 | Assertions.assertFalse(additional.isEnabled());
16 | try (QueryCursor cursor = node.walk(query)) {
17 | for (QueryMatch match: cursor) {
18 | Assertions.assertEquals(1, match.getNodes().size());
19 | Assertions.assertEquals(0, match.getNodes(additional).size());
20 | }
21 | }
22 | Assertions.assertFalse(additional.isEnabled());
23 | additional.disable();
24 | Assertions.assertFalse(additional.isEnabled());
25 | Capture target = captures.get(1);
26 | Assertions.assertTrue(target.isEnabled());
27 | target.disable();
28 | Assertions.assertFalse(target.isEnabled());
29 | try (QueryCursor cursor = node.walk(query)) {
30 | for (QueryMatch match: cursor) {
31 | Assertions.assertEquals(0, match.getNodes().size());
32 | }
33 | }
34 | Assertions.assertFalse(target.isEnabled());
35 | target.disable();
36 | Assertions.assertFalse(target.isEnabled());
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/test/java/ch/usi/si/seart/treesitter/LanguageTest.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import ch.usi.si.seart.treesitter.error.ABIVersionError;
4 | import ch.usi.si.seart.treesitter.version.TreeSitter;
5 | import lombok.Cleanup;
6 | import org.junit.jupiter.api.Assertions;
7 | import org.junit.jupiter.api.Test;
8 | import org.junit.jupiter.api.extension.ExtensionContext;
9 | import org.junit.jupiter.api.io.TempDir;
10 | import org.junit.jupiter.params.ParameterizedTest;
11 | import org.junit.jupiter.params.provider.Arguments;
12 | import org.junit.jupiter.params.provider.ArgumentsProvider;
13 | import org.junit.jupiter.params.provider.ArgumentsSource;
14 | import org.junit.jupiter.params.provider.EnumSource;
15 | import org.junit.jupiter.params.provider.MethodSource;
16 | import org.mockito.Mockito;
17 |
18 | import java.net.URL;
19 | import java.nio.file.Path;
20 | import java.util.Collection;
21 | import java.util.List;
22 | import java.util.stream.Stream;
23 |
24 | class LanguageTest extends BaseTest {
25 |
26 | @TempDir
27 | private static Path tmp;
28 | private static final Language language = Language.PYTHON;
29 |
30 | @ParameterizedTest
31 | @EnumSource(Language.class)
32 | void testValidate(Language language) {
33 | Assertions.assertDoesNotThrow(() -> Language.validate(language));
34 | }
35 |
36 | private static class ValidateExceptionProvider implements ArgumentsProvider {
37 |
38 | @Override
39 | public Stream extends Arguments> provideArguments(ExtensionContext extensionContext) {
40 | int min = TreeSitter.getMinimumABIVersion();
41 | int max = TreeSitter.getCurrentABIVersion();
42 | Language outdated = Mockito.mock(Language.class);
43 | Mockito.when(outdated.getId()).thenReturn(1L);
44 | Mockito.when(outdated.getVersion()).thenReturn(min - 1);
45 | Language unsupported = Mockito.mock(Language.class);
46 | Mockito.when(unsupported.getId()).thenReturn(1L);
47 | Mockito.when(unsupported.getVersion()).thenReturn(max + 1);
48 | return Stream.of(
49 | Arguments.of(NullPointerException.class, null),
50 | Arguments.of(UnsatisfiedLinkError.class, invalid),
51 | Arguments.of(ABIVersionError.class, outdated),
52 | Arguments.of(ABIVersionError.class, unsupported)
53 | );
54 | }
55 | }
56 |
57 | @ParameterizedTest(name = "[{index}] {0}")
58 | @ArgumentsSource(ValidateExceptionProvider.class)
59 | void testValidateThrows(Class throwableType, Language language) {
60 | Assertions.assertThrows(throwableType, () -> Language.validate(language));
61 | }
62 |
63 | @ParameterizedTest
64 | @EnumSource(Language.class)
65 | void testGetMetadata(Language language) {
66 | Language.Metadata metadata = language.getMetadata();
67 | Assertions.assertNotNull(metadata);
68 | String sha = metadata.getSHA();
69 | Assertions.assertNotNull(sha);
70 | Assertions.assertEquals(40, sha.length());
71 | URL url = metadata.getURL();
72 | Assertions.assertNotNull(url);
73 | }
74 |
75 | private static class AssociatedWithProvider implements ArgumentsProvider {
76 |
77 | @Override
78 | public Stream extends Arguments> provideArguments(ExtensionContext extensionContext) {
79 | return Stream.of(
80 | Arguments.of(".keep", List.of()),
81 | Arguments.of("requirements.txt", List.of(Language.REQUIREMENTS)),
82 | Arguments.of(".py", List.of(Language.PYTHON)),
83 | Arguments.of(".gitattributes", List.of(Language.GITATTRIBUTES)),
84 | Arguments.of("__init__.py", List.of(Language.PYTHON)),
85 | Arguments.of(".gitignore", List.of(Language.GITIGNORE)),
86 | Arguments.of("Main.java", List.of(Language.JAVA)),
87 | Arguments.of("Dockerfile", List.of(Language.DOCKERFILE)),
88 | Arguments.of("example.h", List.of(
89 | Language.C,
90 | Language.CPP,
91 | Language.OBJECTIVE_C
92 | ))
93 | );
94 | }
95 | }
96 |
97 | @ParameterizedTest(name = "[{index}] {0}")
98 | @ArgumentsSource(AssociatedWithProvider.class)
99 | void testAssociatedWith(String name, List expected) {
100 | Path path = Path.of(tmp.toString(), name);
101 | Collection actual = Language.associatedWith(path);
102 | Assertions.assertNotNull(actual);
103 | Assertions.assertEquals(expected.size(), actual.size());
104 | Assertions.assertEquals(expected, actual);
105 | Assertions.assertThrows(UnsupportedOperationException.class, () -> actual.add(null));
106 | }
107 |
108 | private static class AssociatedWithExceptionProvider implements ArgumentsProvider {
109 |
110 | @Override
111 | public Stream extends Arguments> provideArguments(ExtensionContext extensionContext) {
112 | return Stream.of(
113 | Arguments.of(NullPointerException.class, null),
114 | Arguments.of(IllegalArgumentException.class, tmp)
115 | );
116 | }
117 | }
118 |
119 | @ParameterizedTest(name = "[{index}] {0}")
120 | @ArgumentsSource(AssociatedWithExceptionProvider.class)
121 | void testAssociatedWithThrows(Class throwableType, Path path) {
122 | Assertions.assertThrows(throwableType, () -> Language.associatedWith(path));
123 | }
124 |
125 | @Test
126 | void testNextState() {
127 | @Cleanup Parser parser = Parser.getFor(language);
128 | @Cleanup Tree tree = parser.parse("pass");
129 | Node root = tree.getRootNode();
130 | Assertions.assertEquals(0, language.nextState(root));
131 | }
132 |
133 | private static Stream invalidNodes() {
134 | return Stream.of(
135 | Arguments.of(NullPointerException.class, null),
136 | Arguments.of(IllegalArgumentException.class, empty)
137 | );
138 | }
139 |
140 | @ParameterizedTest(name = "[{index}] {0}")
141 | @MethodSource("invalidNodes")
142 | void testNextStateThrows(Class throwableType, Node node) {
143 | Assertions.assertThrows(throwableType, () -> language.nextState(node));
144 | Assertions.assertThrows(throwableType, () -> invalid.nextState(node));
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/test/java/ch/usi/si/seart/treesitter/LookaheadIteratorTest.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import org.junit.jupiter.api.Assertions;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import java.util.List;
7 | import java.util.NoSuchElementException;
8 | import java.util.Spliterator;
9 | import java.util.Spliterators;
10 | import java.util.stream.Collectors;
11 | import java.util.stream.StreamSupport;
12 |
13 | class LookaheadIteratorTest extends BaseTest {
14 |
15 | private static final Language language = Language.JAVA;
16 |
17 | @Test
18 | void testIterator() {
19 | try (LookaheadIterator iterator = language.iterator(0)) {
20 | Spliterator spliterator = Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED);
21 | List symbols = StreamSupport.stream(spliterator, false).collect(Collectors.toUnmodifiableList());
22 | Assertions.assertFalse(symbols.isEmpty());
23 | Assertions.assertTrue(language.getSymbols().containsAll(symbols));
24 | Assertions.assertThrows(NoSuchElementException.class, iterator::next);
25 | } catch (NoSuchElementException ignored) {
26 | Assertions.fail();
27 | }
28 | }
29 |
30 | @Test
31 | @SuppressWarnings("resource")
32 | void testIteratorThrows() {
33 | int states = language.getTotalStates();
34 | Assertions.assertThrows(IllegalArgumentException.class, () -> language.iterator(Integer.MIN_VALUE));
35 | Assertions.assertThrows(IllegalArgumentException.class, () -> language.iterator(-1));
36 | Assertions.assertThrows(IllegalArgumentException.class, () -> language.iterator(states));
37 | Assertions.assertThrows(IllegalArgumentException.class, () -> language.iterator(Integer.MAX_VALUE));
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/test/java/ch/usi/si/seart/treesitter/MultiCaptureTest.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import lombok.Cleanup;
4 | import org.junit.jupiter.api.Assertions;
5 |
6 | import java.util.List;
7 |
8 | class MultiCaptureTest extends AbstractQueryTest {
9 |
10 | @Override
11 | protected void assertions(Node node, Query query) {
12 | List captures = query.getCaptures();
13 | Capture additional = captures.get(0);
14 | Capture target = captures.get(1);
15 | @Cleanup QueryCursor cursor = node.walk(query);
16 | for (QueryMatch match: cursor) {
17 | Assertions.assertEquals(3, match.getNodes().size());
18 | Assertions.assertEquals(2, match.getNodes(additional.getName()).size());
19 | Assertions.assertEquals(1, match.getNodes(target.getName()).size());
20 | Assertions.assertEquals(2, match.getNodes(additional).size());
21 | Assertions.assertEquals(1, match.getNodes(target).size());
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/test/java/ch/usi/si/seart/treesitter/OffsetTreeCursorTest.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import ch.usi.si.seart.treesitter.printer.PrinterTestBase;
4 | import ch.usi.si.seart.treesitter.printer.SyntaxTreePrinter;
5 | import ch.usi.si.seart.treesitter.printer.TreePrinter;
6 | import org.junit.jupiter.api.Assertions;
7 | import org.junit.jupiter.api.Disabled;
8 | import org.junit.jupiter.api.Test;
9 |
10 | class OffsetTreeCursorTest extends PrinterTestBase {
11 |
12 | @Test
13 | @SuppressWarnings("resource")
14 | void testCursorThrows() {
15 | Assertions.assertThrows(NullPointerException.class, () -> new OffsetTreeCursor(null, _1_1_));
16 | Assertions.assertThrows(NullPointerException.class, () -> new OffsetTreeCursor(treeless, null));
17 | Assertions.assertThrows(IllegalStateException.class, () -> new OffsetTreeCursor(treeless, _1_1_));
18 | }
19 |
20 | @Disabled("No need test the throw here")
21 | @Override
22 | protected void testPrinterThrows() {
23 | }
24 |
25 | @Override
26 | protected TreeCursor getCursor(Tree tree) {
27 | Node root = tree.getRootNode();
28 | Node method = root.getChild(1).getChildByFieldName("body").getChild(1);
29 | return new OffsetTreeCursor(method, new Point(-1, -2));
30 | }
31 |
32 | @Override
33 | protected String getExpected() {
34 | return "method_declaration [2:2] - [5:3]\n" +
35 | " modifiers [2:2] - [2:15]\n" +
36 | " type: void_type [2:16] - [2:20]\n" +
37 | " name: identifier [2:21] - [2:25]\n" +
38 | " parameters: formal_parameters [2:25] - [2:40]\n" +
39 | " formal_parameter [2:26] - [2:39]\n" +
40 | " type: array_type [2:26] - [2:34]\n" +
41 | " element: type_identifier [2:26] - [2:32]\n" +
42 | " dimensions: dimensions [2:32] - [2:34]\n" +
43 | " name: identifier [2:35] - [2:39]\n" +
44 | " body: block [2:41] - [5:3]\n" +
45 | " line_comment [3:6] - [3:20]\n" +
46 | " expression_statement [4:6] - [4:42]\n" +
47 | " method_invocation [4:6] - [4:41]\n" +
48 | " object: field_access [4:6] - [4:16]\n" +
49 | " object: identifier [4:6] - [4:12]\n" +
50 | " field: identifier [4:13] - [4:16]\n" +
51 | " name: identifier [4:17] - [4:24]\n" +
52 | " arguments: argument_list [4:24] - [4:41]\n" +
53 | " string_literal [4:25] - [4:40]\n" +
54 | " string_fragment [4:26] - [4:39]\n";
55 | }
56 |
57 | @Override
58 | protected TreePrinter getPrinter(TreeCursor cursor) {
59 | return new SyntaxTreePrinter(cursor);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/test/java/ch/usi/si/seart/treesitter/PatternTest.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import lombok.Cleanup;
4 | import org.junit.jupiter.api.Assertions;
5 | import org.junit.jupiter.api.Test;
6 | import org.junit.jupiter.params.ParameterizedTest;
7 | import org.junit.jupiter.params.provider.ValueSource;
8 |
9 | import java.util.List;
10 |
11 | class PatternTest extends BaseTest {
12 |
13 | @ParameterizedTest(name = "[{index}] {0}")
14 | @ValueSource(strings = {
15 | "(line_comment)(block_comment)",
16 | "(line_comment) (block_comment)",
17 | "(line_comment ) (block_comment )",
18 | "(line_comment ) ( block_comment)",
19 | "( line_comment) (block_comment )",
20 | "( line_comment ) ( block_comment )",
21 | "(line_comment) (block_comment)",
22 | "(line_comment )( block_comment)",
23 | " (line_comment) (block_comment) ",
24 | })
25 | void test(String string) {
26 | List parts = List.of("(line_comment)", "(block_comment)");
27 | @Cleanup Query query = Query.builder()
28 | .language(Language.JAVA)
29 | .pattern(string)
30 | .build();
31 | Assertions.assertEquals(String.join(" ", parts), query.getPattern());
32 | List patterns = query.getPatterns();
33 | Assertions.assertFalse(patterns.isEmpty());
34 | Assertions.assertEquals(2, patterns.size());
35 | for (int i = 0; i < patterns.size(); i++) {
36 | Pattern pattern = patterns.get(i);
37 | Assertions.assertTrue(pattern.getStartOffset() >= 0);
38 | Assertions.assertTrue(pattern.getStartOffset() < pattern.getEndOffset());
39 | String actual = pattern.getValue();
40 | String expected = parts.get(i);
41 | Assertions.assertEquals(expected, actual);
42 | }
43 | }
44 |
45 | @Test
46 | void testDisable() {
47 | @Cleanup Parser parser = Parser.getFor(Language.PYTHON);
48 | @Cleanup Tree tree = parser.parse("pass");
49 | @Cleanup Query query = Query.builder()
50 | .language(Language.PYTHON)
51 | .pattern("(module) @capture")
52 | .build();
53 | Node root = tree.getRootNode();
54 | Pattern pattern = query.getPatterns().stream()
55 | .findFirst()
56 | .orElseGet(Assertions::fail);
57 | Assertions.assertTrue(pattern.isEnabled());
58 | pattern.disable();
59 | Assertions.assertFalse(pattern.isEnabled());
60 | @Cleanup QueryCursor cursor = root.walk(query);
61 | for (QueryMatch ignored: cursor) Assertions.fail();
62 | Assertions.assertFalse(pattern.isEnabled());
63 | pattern.disable();
64 | Assertions.assertFalse(pattern.isEnabled());
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/test/java/ch/usi/si/seart/treesitter/PointTest.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import org.junit.jupiter.api.Assertions;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import java.util.ArrayList;
7 | import java.util.Collections;
8 | import java.util.List;
9 |
10 | class PointTest extends BaseTest {
11 |
12 | @Test
13 | void testIsOrigin() {
14 | Assertions.assertTrue(_0_0_.isOrigin());
15 | }
16 |
17 | @Test
18 | void testIsNotOrigin() {
19 | Assertions.assertFalse(_1_0_.isOrigin());
20 | Assertions.assertFalse(_0_1_.isOrigin());
21 | Assertions.assertFalse(_1_1_.isOrigin());
22 | Assertions.assertFalse(_2_2_.isOrigin());
23 | }
24 |
25 | @Test
26 | void testCompareTo() {
27 | List sorted = List.of(new Point(-1, -1), new Point(-1, 0), _0_0_, _0_1_, _1_0_, _1_1_);
28 | ArrayList unsorted = new ArrayList<>(sorted);
29 | Collections.shuffle(unsorted);
30 | Collections.sort(unsorted);
31 | Assertions.assertEquals(sorted, unsorted);
32 | }
33 |
34 | @Test
35 | void testCompareToThrows() {
36 | Assertions.assertThrows(NullPointerException.class, () -> _0_0_.compareTo(null));
37 | }
38 |
39 | @Test
40 | void testAdd() {
41 | Assertions.assertEquals(_1_1_, _0_0_.add(_1_1_));
42 | Assertions.assertEquals(_1_1_, _1_1_.add(_0_0_));
43 | Assertions.assertEquals(_0_0_, new Point(-1, -1).add(_1_1_));
44 | Assertions.assertEquals(_2_2_, _1_1_.add(_1_1_));
45 | }
46 |
47 | @Test
48 | void testSubtract() {
49 | Assertions.assertEquals(_0_0_, _1_1_.subtract(_1_1_));
50 | Assertions.assertEquals(_1_1_, _1_1_.subtract(_0_0_));
51 | Assertions.assertEquals(new Point(-1, -1), _0_0_.subtract(_1_1_));
52 | Assertions.assertEquals(new Point(-2, -2), new Point(-1, -1).subtract(_1_1_));
53 | }
54 |
55 | @Test
56 | void testMultiply() {
57 | Assertions.assertEquals(_0_0_, _0_0_.multiply(2));
58 | Assertions.assertEquals(_0_0_, _1_1_.multiply(0));
59 | Assertions.assertEquals(_1_1_, _1_1_.multiply(1));
60 | Assertions.assertEquals(_2_2_, _1_1_.multiply(2));
61 | }
62 |
63 | @Test
64 | void testThrows() {
65 | Assertions.assertThrows(NullPointerException.class, () -> _0_0_.add(null));
66 | Assertions.assertThrows(NullPointerException.class, () -> _0_0_.subtract(null));
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/test/java/ch/usi/si/seart/treesitter/PredicateTest.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import org.junit.jupiter.api.AfterAll;
4 | import org.junit.jupiter.api.Assertions;
5 | import org.junit.jupiter.api.Test;
6 |
7 | import java.util.List;
8 |
9 | class PredicateTest extends BaseTest {
10 |
11 | private static final String expected = "(#any-of? @variable \"document\" \"window\" \"console\")";
12 |
13 | private static final Query query = Query.builder()
14 | .language(Language.JAVASCRIPT)
15 | .pattern("((identifier) @variable "+expected+")")
16 | .build();
17 |
18 | private static final Pattern pattern = query.getPatterns().stream()
19 | .findFirst()
20 | .orElseThrow();
21 |
22 | @AfterAll
23 | static void afterAll() {
24 | query.close();
25 | }
26 |
27 | @Test
28 | void test() {
29 | Predicate predicate = pattern.getPredicates().stream()
30 | .findFirst()
31 | .orElseGet(Assertions::fail);
32 | List steps = predicate.getSteps();
33 | Assertions.assertFalse(steps.isEmpty());
34 | Assertions.assertEquals(6, steps.size());
35 | Assertions.assertEquals(
36 | Predicate.Step.Type.STRING,
37 | steps.stream()
38 | .map(Predicate.Step::getType)
39 | .findFirst()
40 | .orElse(null)
41 | );
42 | Assertions.assertEquals(expected, predicate.toString());
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/test/java/ch/usi/si/seart/treesitter/QuantifierTest.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import lombok.Cleanup;
4 | import org.junit.jupiter.api.Assertions;
5 | import org.junit.jupiter.api.Test;
6 | import org.junit.jupiter.api.extension.ExtensionContext;
7 | import org.junit.jupiter.params.ParameterizedTest;
8 | import org.junit.jupiter.params.provider.Arguments;
9 | import org.junit.jupiter.params.provider.ArgumentsProvider;
10 | import org.junit.jupiter.params.provider.ArgumentsSource;
11 |
12 | import java.util.List;
13 | import java.util.Map;
14 | import java.util.stream.Stream;
15 |
16 | class QuantifierTest extends BaseTest {
17 |
18 | private static final Language language = Language.JAVA;
19 |
20 | private static final Map symbols = Map.of(
21 | Quantifier.ONE, "",
22 | Quantifier.ONE_OR_MORE, "+",
23 | Quantifier.ZERO_OR_ONE, "?",
24 | Quantifier.ZERO_OR_MORE, "*"
25 | );
26 |
27 | private static final class QuantifierArgumentsProvider implements ArgumentsProvider {
28 |
29 | @Override
30 | public Stream extends Arguments> provideArguments(ExtensionContext context) {
31 | return symbols.entrySet().stream()
32 | .map(entry -> {
33 | Quantifier quantifier = entry.getKey();
34 | String symbol = entry.getValue();
35 | String pattern = "((_)" + symbol + " @capture)";
36 | Query query = Query.getFor(language, pattern);
37 | List captures = query.getCaptures();
38 | Capture capture = captures.stream().findFirst().orElseThrow();
39 | return Arguments.of(quantifier, capture, query);
40 | });
41 | }
42 | }
43 |
44 | @ParameterizedTest(name = "[{index}] {0}")
45 | @ArgumentsSource(QuantifierArgumentsProvider.class)
46 | void testGetQuantifiers(Quantifier expected, Capture capture, Query query) {
47 | List quantifiers = capture.getQuantifiers();
48 | Assertions.assertNotNull(quantifiers);
49 | Assertions.assertFalse(quantifiers.isEmpty());
50 | Assertions.assertEquals(1, quantifiers.size());
51 | Quantifier actual = quantifiers.stream()
52 | .findFirst()
53 | .orElseGet(Assertions::fail);
54 | Assertions.assertEquals(expected, actual);
55 | query.close();
56 | }
57 |
58 | @Test
59 | void testGetQuantifier() {
60 | @Cleanup Query query = Query.builder()
61 | .language(language)
62 | .pattern("((_) @capture)")
63 | .pattern("(identifier)")
64 | .build();
65 | List captures = query.getCaptures();
66 | List patterns = query.getPatterns();
67 | Capture capture = captures.stream().findFirst().orElseThrow();
68 | Pattern pattern = patterns.stream().skip(1).findFirst().orElseThrow();
69 | Quantifier quantifier = capture.getQuantifier(pattern);
70 | Assertions.assertEquals(Quantifier.ZERO, quantifier);
71 | }
72 |
73 | @Test
74 | void testGetQuantifierThrows() {
75 | @Cleanup Query original = Query.getFor(language, "((_) @capture)");
76 | @Cleanup Query copy = Query.getFor(language, "((_) @capture)");
77 | List captures = original.getCaptures();
78 | Capture capture = captures.stream().findFirst().orElseThrow();
79 | List patterns = copy.getPatterns();
80 | Pattern pattern = patterns.stream().findFirst().orElseThrow();
81 | Assertions.assertThrows(NullPointerException.class, () -> capture.getQuantifier(null));
82 | Assertions.assertThrows(IllegalArgumentException.class, () -> capture.getQuantifier(pattern));
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/test/java/ch/usi/si/seart/treesitter/QueryTest.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import ch.usi.si.seart.treesitter.exception.query.QueryCaptureException;
4 | import ch.usi.si.seart.treesitter.exception.query.QueryFieldException;
5 | import ch.usi.si.seart.treesitter.exception.query.QueryNodeTypeException;
6 | import ch.usi.si.seart.treesitter.exception.query.QueryStructureException;
7 | import ch.usi.si.seart.treesitter.exception.query.QuerySyntaxException;
8 | import lombok.Cleanup;
9 | import org.junit.jupiter.api.Assertions;
10 | import org.junit.jupiter.api.Test;
11 | import org.junit.jupiter.api.extension.ExtensionContext;
12 | import org.junit.jupiter.params.ParameterizedTest;
13 | import org.junit.jupiter.params.provider.Arguments;
14 | import org.junit.jupiter.params.provider.ArgumentsProvider;
15 | import org.junit.jupiter.params.provider.ArgumentsSource;
16 |
17 | import java.util.Arrays;
18 | import java.util.List;
19 | import java.util.Map;
20 | import java.util.function.Supplier;
21 | import java.util.stream.Stream;
22 |
23 | class QueryTest extends BaseTest {
24 |
25 | private static final Language language = Language.JAVA;
26 |
27 | private static class QueryProvider implements ArgumentsProvider {
28 |
29 | @Override
30 | public Stream extends Arguments> provideArguments(ExtensionContext extensionContext) {
31 |
32 | String pattern1 = "(_)";
33 | String pattern2 = "(_) @capture";
34 | String pattern3 = "\"return\" @capture";
35 | String pattern4 = "\"private\" @capture.first";
36 | String pattern5 = "\"public\" @capture.second";
37 |
38 | return Stream.of(
39 | Arguments.of("", Query.builder().language(language).build(), 0, 0, 0),
40 | Arguments.of(pattern1, Query.getFor(language, pattern1), 1, 0, 0),
41 | Arguments.of(pattern2, Query.getFor(language, pattern2), 1, 1, 0),
42 | Arguments.of(pattern3, Query.getFor(language, pattern3), 1, 1, 0),
43 | Arguments.of(pattern4, Query.getFor(language, pattern4, pattern5), 2, 2, 0)
44 | );
45 | }
46 | }
47 |
48 | @ParameterizedTest(name = "[{index}] {0}")
49 | @ArgumentsSource(QueryProvider.class)
50 | void testQuery(String ignored, Query query, int patterns, int captures, int strings) {
51 | Assertions.assertNotNull(query);
52 | Assertions.assertFalse(query.isNull());
53 | Assertions.assertEquals(patterns, query.getPatterns().size());
54 | Assertions.assertEquals(captures, query.getCaptures().size());
55 | Assertions.assertEquals(strings, query.getStrings().size());
56 | Assertions.assertEquals(captures > 0, query.hasCaptures());
57 | query.close();
58 | Assertions.assertTrue(query.isNull());
59 | }
60 |
61 | private static class QueryExceptionProvider implements ArgumentsProvider {
62 |
63 | @Override
64 | public Stream extends Arguments> provideArguments(ExtensionContext extensionContext) {
65 | return Stream.of(
66 | Arguments.of(UnsatisfiedLinkError.class, invalid, "(_)"),
67 | Arguments.of(QueryCaptureException.class, Language.JAVA, "(#eq? @key @value)"),
68 | Arguments.of(QueryFieldException.class, Language.JAVA, "(program unknown: (_))"),
69 | Arguments.of(QueryNodeTypeException.class, Language.JAVA, "(null)"),
70 | Arguments.of(QueryStructureException.class, Language.JAVA, "(program (program))"),
71 | Arguments.of(QuerySyntaxException.class, Language.JAVA, "()")
72 | );
73 | }
74 | }
75 |
76 | @ParameterizedTest(name = "[{index}] {0}")
77 | @ArgumentsSource(QueryExceptionProvider.class)
78 | void testQueryException(Class throwableType, Language language, String pattern) {
79 | Assertions.assertThrows(throwableType, () -> Query.getFor(language, pattern));
80 | }
81 |
82 | private static class QuerySupplierExceptionProvider implements ArgumentsProvider {
83 |
84 | @Override
85 | public Stream extends Arguments> provideArguments(ExtensionContext extensionContext) {
86 | Query.Builder builder = Query.builder();
87 | Map> map = Map.of(
88 | "Nothing specified", builder::build,
89 | "No language specified", () -> builder.pattern("(_)").build(),
90 | "Single null pattern string", () -> builder.pattern(null).build(),
91 | "String array as null", () -> builder.patterns((String[]) null).build(),
92 | "String list as null", () -> builder.patterns((List) null).build(),
93 | "String array contains null", () -> builder.patterns("(_)", null).build(),
94 | "String list contains null", () -> builder.patterns(Arrays.asList("(_)", null)).build()
95 | );
96 | return map.entrySet().stream().map(entry -> Arguments.of(entry.getKey(), entry.getValue()));
97 | }
98 | }
99 |
100 | @ParameterizedTest(name = "[{index}] {0}")
101 | @ArgumentsSource(QuerySupplierExceptionProvider.class)
102 | void testQueryException(String ignored, Supplier supplier) {
103 | Assertions.assertThrows(NullPointerException.class, supplier::get);
104 | }
105 |
106 | @Test
107 | void testQueryToBuilder() {
108 | @Cleanup Query original = Query.builder()
109 | .language(language)
110 | .pattern("(line_comment)")
111 | .build();
112 | Query.Builder builder = original.toBuilder();
113 | @Cleanup Query modified = builder
114 | .pattern("(block_comment)")
115 | .build();
116 | Assertions.assertNotEquals(original, modified);
117 | Assertions.assertEquals(1, original.getPatterns().size());
118 | Assertions.assertEquals(2, modified.getPatterns().size());
119 | Assertions.assertEquals(original.getLanguage(), modified.getLanguage());
120 | @Cleanup Query overwrite = builder
121 | .patterns(List.of("(_)"))
122 | .build();
123 | Assertions.assertNotEquals(original, overwrite);
124 | Assertions.assertEquals(original.getPatterns().size(), overwrite.getPatterns().size());
125 | Assertions.assertNotEquals(original.getPattern(), overwrite.getPattern());
126 | Assertions.assertEquals(original.getLanguage(), modified.getLanguage());
127 | @Cleanup Query empty = builder.pattern().build();
128 | Assertions.assertNotEquals(original, empty);
129 | Assertions.assertEquals(1, original.getPatterns().size());
130 | Assertions.assertEquals(0, empty.getPatterns().size());
131 | Assertions.assertEquals(original.getLanguage(), empty.getLanguage());
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/test/java/ch/usi/si/seart/treesitter/RangeTest.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import org.junit.jupiter.api.Assertions;
4 | import org.junit.jupiter.api.Test;
5 | import org.junit.jupiter.api.extension.ExtensionContext;
6 | import org.junit.jupiter.api.function.Executable;
7 | import org.junit.jupiter.params.ParameterizedTest;
8 | import org.junit.jupiter.params.provider.Arguments;
9 | import org.junit.jupiter.params.provider.ArgumentsProvider;
10 | import org.junit.jupiter.params.provider.ArgumentsSource;
11 |
12 | import java.util.stream.Stream;
13 |
14 | class RangeTest extends BaseTest {
15 |
16 | @Test
17 | void testBuilder() {
18 | Range expected = new Range(0, 1, _0_0_, _1_1_);
19 | Range actual = Range.builder()
20 | .startByte(0)
21 | .endByte(1)
22 | .startPoint(_0_0_)
23 | .endPoint(_1_1_)
24 | .build();
25 | Assertions.assertEquals(expected, actual);
26 | Range modified = actual.toBuilder()
27 | .endByte(2)
28 | .endPoint(_2_2_)
29 | .build();
30 | Assertions.assertNotEquals(expected, modified);
31 | }
32 |
33 | private static class BuilderExceptionProvider implements ArgumentsProvider {
34 |
35 | @Override
36 | public Stream extends Arguments> provideArguments(ExtensionContext extensionContext) {
37 | Point negativeRow = new Point(-1, 0);
38 | Point negativeCol = new Point(0, -1);
39 | Executable setNegativeStartByte = () -> Range.builder().startByte(-1);
40 | Executable setNegativeEndByte = () -> Range.builder().endByte(-1);
41 | Executable setNullStartPoint = () -> Range.builder().startPoint(null);
42 | Executable setNullEndPoint = () -> Range.builder().endPoint(null);
43 | Executable setNegativeStartPointRow = () -> Range.builder().startPoint(negativeRow);
44 | Executable setNegativeEndPointRow = () -> Range.builder().endPoint(negativeRow);
45 | Executable setNegativeStartPointCol = () -> Range.builder().startPoint(negativeCol);
46 | Executable setNegativeEndPointCol = () -> Range.builder().endPoint(negativeCol);
47 | Executable buildReversedByteRange = () -> Range.builder().startByte(2).endByte(0).build();
48 | Executable buildReversedPointRange = () -> Range.builder().startPoint(_1_1_).endPoint(_0_0_).build();
49 | return Stream.of(
50 | Arguments.of(IllegalArgumentException.class, setNegativeStartByte),
51 | Arguments.of(IllegalArgumentException.class, setNegativeEndByte),
52 | Arguments.of(NullPointerException.class, setNullStartPoint),
53 | Arguments.of(NullPointerException.class, setNullEndPoint),
54 | Arguments.of(IllegalArgumentException.class, setNegativeStartPointRow),
55 | Arguments.of(IllegalArgumentException.class, setNegativeEndPointRow),
56 | Arguments.of(IllegalArgumentException.class, setNegativeStartPointCol),
57 | Arguments.of(IllegalArgumentException.class, setNegativeEndPointCol),
58 | Arguments.of(IllegalArgumentException.class, buildReversedByteRange),
59 | Arguments.of(IllegalArgumentException.class, buildReversedPointRange)
60 | );
61 | }
62 | }
63 |
64 | @ParameterizedTest(name = "[{index}] {0}")
65 | @ArgumentsSource(BuilderExceptionProvider.class)
66 | void testBuilderThrows(Class exception, Executable runnable) {
67 | Assertions.assertThrows(exception, runnable);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/test/java/ch/usi/si/seart/treesitter/TreeTest.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter;
2 |
3 | import lombok.Cleanup;
4 | import org.junit.jupiter.api.AfterAll;
5 | import org.junit.jupiter.api.AfterEach;
6 | import org.junit.jupiter.api.Assertions;
7 | import org.junit.jupiter.api.BeforeAll;
8 | import org.junit.jupiter.api.BeforeEach;
9 | import org.junit.jupiter.api.Test;
10 | import org.junit.jupiter.api.extension.ExtensionContext;
11 | import org.junit.jupiter.params.ParameterizedTest;
12 | import org.junit.jupiter.params.provider.Arguments;
13 | import org.junit.jupiter.params.provider.ArgumentsProvider;
14 | import org.junit.jupiter.params.provider.ArgumentsSource;
15 |
16 | import java.util.List;
17 | import java.util.stream.Stream;
18 |
19 | class TreeTest extends BaseTest {
20 |
21 | private static final String source = "class Main {\n // This is a line comment\n}\n";
22 | private static final String target = "class Main {\n}\n";
23 | private static Parser parser;
24 | private Tree tree;
25 | private Node root;
26 |
27 | @BeforeAll
28 | static void beforeAll() {
29 | parser = Parser.getFor(Language.JAVA);
30 | }
31 |
32 | @BeforeEach
33 | void setUp() {
34 | tree = parser.parse(source);
35 | root = tree.getRootNode();
36 | }
37 |
38 | @AfterEach
39 | void tearDown() {
40 | tree.close();
41 | }
42 |
43 | @AfterAll
44 | static void afterAll() {
45 | parser.close();
46 | }
47 |
48 | @Test
49 | void testGetSource() {
50 | Assertions.assertEquals(source, tree.getSource());
51 | }
52 |
53 | private static class ByteRangeContentProvider implements ArgumentsProvider {
54 |
55 | @Override
56 | public Stream extends Arguments> provideArguments(ExtensionContext extensionContext) {
57 | Tree tree = parser.parse(source);
58 | Node root = tree.getRootNode();
59 | Node name = root.getChild(0).getChildByFieldName("name");
60 | Node body = root.getChild(0).getChildByFieldName("body");
61 | Node leftCurly = body.getChild(0);
62 | Node comment = body.getChild(1);
63 | Node rightCurly = body.getChild(2);
64 | return Stream.of(
65 | Arguments.of(0, 45, root),
66 | Arguments.of(6, 10, name),
67 | Arguments.of(11, 12, leftCurly),
68 | Arguments.of(17, 42, comment),
69 | Arguments.of(43, 44, rightCurly)
70 | );
71 | }
72 | }
73 |
74 | @ParameterizedTest(name = "[{index}] {0} - {1}")
75 | @ArgumentsSource(ByteRangeContentProvider.class)
76 | void testGetSourceStartEnd(int beginIndex, int endIndex, Node node) {
77 | String expected = source.substring(beginIndex, endIndex);
78 | String actual = tree.getSource(node.getStartByte(), node.getEndByte());
79 | Assertions.assertEquals(expected, actual);
80 | }
81 |
82 | @Test
83 | void testEdit() {
84 | Assertions.assertEquals(new Point(0, 0), root.getStartPoint());
85 | Assertions.assertEquals(new Point(3, 0), root.getEndPoint());
86 | Assertions.assertFalse(root.hasChanges());
87 | InputEdit inputEdit = new InputEdit(
88 | source.indexOf("// This is a line comment"),
89 | source.length(),
90 | target.length(),
91 | new Point(1, 4), // comment start
92 | new Point(3, 0), // old root end
93 | new Point(2, 0) // new root end
94 | );
95 | tree.edit(inputEdit);
96 | Assertions.assertTrue(root.hasChanges());
97 | Tree modified = parser.parse(target, tree);
98 | List ranges = tree.getChangedRanges(modified);
99 | Assertions.assertNotNull(ranges);
100 | Assertions.assertEquals(1, ranges.size());
101 | Range range = ranges.stream().findFirst().orElseGet(Assertions::fail);
102 | Assertions.assertEquals(new Point(1, 0), range.getStartPoint());
103 | Assertions.assertEquals(new Point(2, 0), range.getEndPoint());
104 | root = modified.getRootNode();
105 | Assertions.assertEquals("program", root.getType());
106 | Assertions.assertEquals(new Point(0, 0), root.getStartPoint());
107 | Assertions.assertEquals(new Point(2, 0), root.getEndPoint());
108 | Assertions.assertFalse(root.hasChanges());
109 | }
110 |
111 | @Test
112 | void testClone() {
113 | @Cleanup Tree copy = tree.clone();
114 | Assertions.assertNotEquals(tree, copy);
115 | }
116 |
117 | @Test
118 | void testConstructorThrows() {
119 | @Cleanup Tree tree = new Tree(0L, Language.JAVA, "");
120 | Assertions.assertThrows(NullPointerException.class, () -> tree.edit(null));
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/test/java/ch/usi/si/seart/treesitter/printer/DotGraphPrinterTest.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.printer;
2 |
3 | import ch.usi.si.seart.treesitter.BaseTest;
4 | import ch.usi.si.seart.treesitter.Language;
5 | import ch.usi.si.seart.treesitter.Parser;
6 | import ch.usi.si.seart.treesitter.Tree;
7 | import lombok.Cleanup;
8 | import lombok.SneakyThrows;
9 | import org.junit.jupiter.api.Assertions;
10 | import org.junit.jupiter.api.Test;
11 |
12 | import java.io.File;
13 | import java.io.IOException;
14 | import java.nio.file.Files;
15 | import java.nio.file.Path;
16 |
17 | class DotGraphPrinterTest extends BaseTest {
18 |
19 | private static final String source =
20 | "package ch.usi.si;\n" +
21 | "\n" +
22 | "public class Main {\n" +
23 | " public static void main(String[] args) {\n" +
24 | " //line comment\n" +
25 | " System.out.println(\"Hello, World!\");\n" +
26 | " }\n" +
27 | "}";
28 |
29 | @Test
30 | void testPrint() {
31 | @Cleanup Parser parser = Parser.getFor(Language.JAVA);
32 | @Cleanup Tree tree = parser.parse(source);
33 | TreePrinter printer = new DotGraphPrinter(tree);
34 | String actual = printer.print();
35 | assertion(actual);
36 | }
37 |
38 | @Test
39 | @SneakyThrows(IOException.class)
40 | void testExport() {
41 | @Cleanup Parser parser = Parser.getFor(Language.JAVA);
42 | @Cleanup Tree tree = parser.parse(source);
43 | TreePrinter printer = new DotGraphPrinter(tree);
44 | File file = printer.export();
45 | Path path = file.toPath();
46 | String actual = Files.readString(path);
47 | Files.delete(file.toPath());
48 | assertion(actual);
49 | }
50 |
51 | @Test
52 | void testPrinterThrows() {
53 | Assertions.assertThrows(NullPointerException.class, () -> new DotGraphPrinter(null));
54 | }
55 |
56 | private void assertion(String result) {
57 | Assertions.assertEquals(704, result.lines().count());
58 | Assertions.assertTrue(result.startsWith("digraph tree {"));
59 | Assertions.assertTrue(result.endsWith("}\n"));
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/test/java/ch/usi/si/seart/treesitter/printer/PrinterTestBase.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.printer;
2 |
3 | import ch.usi.si.seart.treesitter.BaseTest;
4 | import ch.usi.si.seart.treesitter.Language;
5 | import ch.usi.si.seart.treesitter.Parser;
6 | import ch.usi.si.seart.treesitter.Tree;
7 | import ch.usi.si.seart.treesitter.TreeCursor;
8 | import lombok.Cleanup;
9 | import lombok.SneakyThrows;
10 | import org.junit.jupiter.api.Assertions;
11 | import org.junit.jupiter.api.Test;
12 |
13 | import java.io.File;
14 | import java.io.IOException;
15 | import java.nio.file.Files;
16 | import java.nio.file.Path;
17 |
18 | public abstract class PrinterTestBase extends BaseTest {
19 |
20 | @Test
21 | protected void testPrint() {
22 | String source = getSource();
23 | @Cleanup Parser parser = getParser();
24 | @Cleanup Tree tree = parser.parse(source);
25 | @Cleanup TreeCursor cursor = getCursor(tree);
26 | TreePrinter printer = getPrinter(cursor);
27 | String expected = getExpected();
28 | String actual = printer.print();
29 | Assertions.assertEquals(expected, actual);
30 | }
31 |
32 | @Test
33 | @SneakyThrows(IOException.class)
34 | protected void testExport() {
35 | String source = getSource();
36 | @Cleanup Parser parser = getParser();
37 | @Cleanup Tree tree = parser.parse(source);
38 | @Cleanup TreeCursor cursor = getCursor(tree);
39 | TreePrinter printer = getPrinter(cursor);
40 | String expected = getExpected();
41 | File file = printer.export();
42 | Path path = file.toPath();
43 | String actual = Files.readString(path);
44 | Files.delete(path);
45 | Assertions.assertEquals(expected, actual);
46 | }
47 |
48 | @Test
49 | protected void testPrinterThrows() {
50 | Assertions.assertThrows(NullPointerException.class, () -> getPrinter(null));
51 | }
52 |
53 | protected Language getLanguage() {
54 | return Language.JAVA;
55 | }
56 |
57 | protected Parser getParser() {
58 | return Parser.builder()
59 | .language(getLanguage())
60 | .build();
61 | }
62 |
63 | protected TreeCursor getCursor(Tree tree) {
64 | return tree.getRootNode().walk();
65 | }
66 |
67 | protected String getSource() {
68 | return "package ch.usi.si;\n" +
69 | "\n" +
70 | "public class Main {\n" +
71 | " public static void main(String[] args) {\n" +
72 | " //line comment\n" +
73 | " System.out.println(\"Hello, World!\");\n" +
74 | " }\n" +
75 | "}";
76 | }
77 |
78 | protected abstract String getExpected();
79 |
80 | protected abstract TreePrinter getPrinter(TreeCursor cursor);
81 | }
82 |
--------------------------------------------------------------------------------
/src/test/java/ch/usi/si/seart/treesitter/printer/SymbolicExpressionPrinterTest.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.printer;
2 |
3 | import ch.usi.si.seart.treesitter.TreeCursor;
4 |
5 | class SymbolicExpressionPrinterTest extends PrinterTestBase {
6 |
7 | @Override
8 | protected String getExpected() {
9 | return
10 | "(program " +
11 | "(package_declaration " +
12 | "(scoped_identifier " +
13 | "scope: (scoped_identifier " +
14 | "scope: (identifier) " +
15 | "name: (identifier)" +
16 | ") " +
17 | "name: (identifier)" +
18 | ")" +
19 | ") " +
20 | "(class_declaration " +
21 | "(modifiers) " +
22 | "name: (identifier) " +
23 | "body: (class_body " +
24 | "(method_declaration " +
25 | "(modifiers) " +
26 | "type: (void_type) " +
27 | "name: (identifier) " +
28 | "parameters: (formal_parameters " +
29 | "(formal_parameter " +
30 | "type: (array_type " +
31 | "element: (type_identifier) " +
32 | "dimensions: (dimensions)" +
33 | ") " +
34 | "name: (identifier)" +
35 | ")" +
36 | ") " +
37 | "body: (block " +
38 | "(line_comment) " +
39 | "(expression_statement " +
40 | "(method_invocation " +
41 | "object: (field_access " +
42 | "object: (identifier) " +
43 | "field: (identifier)" +
44 | ") " +
45 | "name: (identifier) " +
46 | "arguments: (argument_list " +
47 | "(string_literal " +
48 | "(string_fragment)" +
49 | ")" +
50 | ")" +
51 | ")" +
52 | ")" +
53 | ")" +
54 | ")" +
55 | ")" +
56 | ")" +
57 | ")";
58 | }
59 |
60 | @Override
61 | protected TreePrinter getPrinter(TreeCursor cursor) {
62 | return new SymbolicExpressionPrinter(cursor);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/test/java/ch/usi/si/seart/treesitter/printer/SyntaxTreePrinterTest.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.printer;
2 |
3 | import ch.usi.si.seart.treesitter.TreeCursor;
4 |
5 | class SyntaxTreePrinterTest extends PrinterTestBase {
6 |
7 | @Override
8 | protected String getExpected() {
9 | return "program [0:0] - [7:1]\n" +
10 | " package_declaration [0:0] - [0:18]\n" +
11 | " scoped_identifier [0:8] - [0:17]\n" +
12 | " scope: scoped_identifier [0:8] - [0:14]\n" +
13 | " scope: identifier [0:8] - [0:10]\n" +
14 | " name: identifier [0:11] - [0:14]\n" +
15 | " name: identifier [0:15] - [0:17]\n" +
16 | " class_declaration [2:0] - [7:1]\n" +
17 | " modifiers [2:0] - [2:6]\n" +
18 | " name: identifier [2:13] - [2:17]\n" +
19 | " body: class_body [2:18] - [7:1]\n" +
20 | " method_declaration [3:4] - [6:5]\n" +
21 | " modifiers [3:4] - [3:17]\n" +
22 | " type: void_type [3:18] - [3:22]\n" +
23 | " name: identifier [3:23] - [3:27]\n" +
24 | " parameters: formal_parameters [3:27] - [3:42]\n" +
25 | " formal_parameter [3:28] - [3:41]\n" +
26 | " type: array_type [3:28] - [3:36]\n" +
27 | " element: type_identifier [3:28] - [3:34]\n" +
28 | " dimensions: dimensions [3:34] - [3:36]\n" +
29 | " name: identifier [3:37] - [3:41]\n" +
30 | " body: block [3:43] - [6:5]\n" +
31 | " line_comment [4:8] - [4:22]\n" +
32 | " expression_statement [5:8] - [5:44]\n" +
33 | " method_invocation [5:8] - [5:43]\n" +
34 | " object: field_access [5:8] - [5:18]\n" +
35 | " object: identifier [5:8] - [5:14]\n" +
36 | " field: identifier [5:15] - [5:18]\n" +
37 | " name: identifier [5:19] - [5:26]\n" +
38 | " arguments: argument_list [5:26] - [5:43]\n" +
39 | " string_literal [5:27] - [5:42]\n" +
40 | " string_fragment [5:28] - [5:41]\n";
41 | }
42 |
43 | @Override
44 | protected TreePrinter getPrinter(TreeCursor cursor) {
45 | return new SyntaxTreePrinter(cursor);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/test/java/ch/usi/si/seart/treesitter/printer/XMLPrinterTest.java:
--------------------------------------------------------------------------------
1 | package ch.usi.si.seart.treesitter.printer;
2 |
3 | import ch.usi.si.seart.treesitter.TreeCursor;
4 |
5 | class XMLPrinterTest extends PrinterTestBase {
6 |
7 | @Override
8 | protected String getExpected() {
9 | return "" +
10 | "" +
11 | "" +
12 | "" +
13 | "" +
14 | "" +
15 | "" +
16 | "" +
17 | "" +
18 | "" +
19 | "" +
20 | "" +
21 | "" +
22 | "" +
23 | "" +
24 | "" +
25 | "" +
26 | "" +
27 | "" +
28 | "" +
29 | "" +
30 | "" +
31 | "" +
32 | "" +
33 | "" +
34 | "" +
35 | "" +
36 | "" +
37 | "" +
38 | "" +
39 | "" +
40 | "" +
41 | "" +
42 | "" +
43 | "" +
44 | "" +
45 | "" +
46 | "" +
47 | "" +
48 | "" +
49 | "" +
50 | "" +
51 | "" +
52 | "" +
53 | "" +
54 | "" +
55 | "" +
56 | "" +
57 | "" +
58 | "" +
59 | "" +
60 | "" +
61 | "" +
62 | "" +
63 | "" +
64 | "" +
65 | "" +
66 | "" +
67 | "" +
68 | "" +
69 | "" +
70 | "" +
71 | "" +
72 | "" +
73 | "";
74 | }
75 |
76 | @Override
77 | protected TreePrinter getPrinter(TreeCursor cursor) {
78 | return new XMLPrinter(cursor);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/test/resources/simplelogger.properties:
--------------------------------------------------------------------------------
1 | org.slf4j.simpleLogger.logFile=System.out
2 | org.slf4j.simpleLogger.defaultLogLevel=debug
3 | org.slf4j.simpleLogger.showThreadName=false
4 | org.slf4j.simpleLogger.showDateTime=true
5 | org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss.SSS
6 |
--------------------------------------------------------------------------------
/upgrade.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | function get_default_branch() {
4 | TARGET="$1"
5 | HEAD=refs/remotes/origin/HEAD
6 | RESULT=$(git -C "$TARGET" symbolic-ref "$HEAD" --short)
7 | echo "$RESULT"
8 | }
9 |
10 | set -e
11 |
12 | SUBMODULE=$1
13 |
14 | if [ -z "$SUBMODULE" ]; then
15 | echo "Usage: $(basename "$0") [checkout_target]"
16 | exit 1
17 | fi
18 |
19 | CHECKOUT_TARGET="${2:-$(get_default_branch "$SUBMODULE")}"
20 |
21 | git -C "$SUBMODULE" fetch --all
22 | git -C "$SUBMODULE" checkout "$CHECKOUT_TARGET"
23 | git add "$SUBMODULE"
24 |
--------------------------------------------------------------------------------
/version.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from argparse import ArgumentParser
4 | from git import Repo as GitRepository
5 | from os import getcwd as cwd
6 | from os.path import dirname, realpath
7 | from os.path import join as path
8 |
9 | __location__ = realpath(path(cwd(), dirname(__file__)))
10 |
11 | if __name__ == "__main__":
12 | parser = ArgumentParser(description="Generate tree-sitter API version class.")
13 | parser.add_argument(
14 | "-o",
15 | "--output",
16 | default=path(__location__, "TreeSitter.java"),
17 | help="Output file path.",
18 | )
19 | args = parser.parse_args()
20 | tree_sitter = path(__location__, "tree-sitter")
21 | with open(args.output, "w") as output, GitRepository(tree_sitter) as repository:
22 | commit = repository.head.commit
23 | tags = [tag for tag in repository.tags if tag.commit == commit]
24 | output.write(f"""\
25 | /*
26 | * MIT License
27 | *
28 | * Copyright (c) 2022-present SEART Research Group and Contributors
29 | *
30 | * Permission is hereby granted, free of charge, to any person obtaining a copy
31 | * of this software and associated documentation files (the "Software"), to deal
32 | * in the Software without restriction, including without limitation the rights
33 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
34 | * copies of the Software, and to permit persons to whom the Software is
35 | * furnished to do so, subject to the following conditions:
36 | *
37 | * The above copyright notice and this permission notice shall be included in all
38 | * copies or substantial portions of the Software.
39 | *
40 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
41 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
42 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
43 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
44 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
45 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
46 | * SOFTWARE.
47 | */
48 | package ch.usi.si.seart.treesitter.version;
49 |
50 | import lombok.experimental.UtilityClass;
51 |
52 | /**
53 | * Utility used for obtaining the current version of the {{@code tree-sitter}} API.
54 | *
55 | * @author Ozren Dabić
56 | * @since 1.11.0
57 | */
58 | @UtilityClass
59 | public class TreeSitter {{
60 |
61 | public static final String SHA = \"{commit.hexsha}\";
62 |
63 | public static final String TAG = \"{next((tag.name for tag in tags), "")}\";
64 |
65 | /**
66 | * Get the current version of {{@code tree-sitter}}.
67 | *
68 | * @return the semantic version string, along with a commit SHA
69 | */
70 | public String getVersion() {{
71 | return TAG + \" (\" + SHA + \")\";
72 | }}
73 |
74 | /**
75 | * The latest ABI version that is supported by the current version of the library.
76 | * When languages are generated by the {{@code tree-sitter}} CLI, they are assigned
77 | * an ABI version number that corresponds to the current CLI version.
78 | *
79 | * @return current ABI version number
80 | * @since 1.12.0
81 | */
82 | public native int getCurrentABIVersion();
83 |
84 | /**
85 | * The earliest ABI version that is supported by the current version of the library.
86 | * The {{@code tree-sitter}} library is generally backwards-compatible with languages
87 | * generated using older CLI versions, but is not forwards-compatible.
88 | *
89 | * @return earliest supported ABI version number
90 | * @since 1.12.0
91 | */
92 | public native int getMinimumABIVersion();
93 | }}
94 | """)
95 |
--------------------------------------------------------------------------------