From 8b398e678bea02000f61c38dc144c76b9cac6ae6 Mon Sep 17 00:00:00 2001 From: David Baker Effendi Date: Fri, 20 Mar 2026 09:39:59 +0200 Subject: [PATCH 1/4] fix: throw IllegalStateException if sourceBytes missing in TSQueryPredicate --- .../java/org/treesitter/TSQueryPredicate.java | 8 ++++---- .../org/treesitter/TSQueryPredicateTest.java | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/tree-sitter/src/main/java/org/treesitter/TSQueryPredicate.java b/tree-sitter/src/main/java/org/treesitter/TSQueryPredicate.java index c00cde2..8c61a89 100644 --- a/tree-sitter/src/main/java/org/treesitter/TSQueryPredicate.java +++ b/tree-sitter/src/main/java/org/treesitter/TSQueryPredicate.java @@ -41,12 +41,12 @@ public String getName() { */ public boolean test(TSQueryMatch match, byte[] sourceBytes) { return test(match, n -> { - if (n == null || n.isNull() || sourceBytes == null) return ""; + if (n == null || n.isNull()) return ""; + if (sourceBytes == null || n.getStartByte() < 0 || n.getStartByte() > n.getEndByte() || n.getStartByte() >= sourceBytes.length) { + throw new IllegalStateException("Source bytes are required to evaluate text-based predicates"); + } int start = n.getStartByte(); int end = n.getEndByte(); - if (start < 0 || start > end || start >= sourceBytes.length) { - return ""; - } int length = Math.min(end, sourceBytes.length) - start; return new String(sourceBytes, start, length, java.nio.charset.StandardCharsets.UTF_8); }); diff --git a/tree-sitter/src/test/java/org/treesitter/TSQueryPredicateTest.java b/tree-sitter/src/test/java/org/treesitter/TSQueryPredicateTest.java index b538450..6bbc6df 100644 --- a/tree-sitter/src/test/java/org/treesitter/TSQueryPredicateTest.java +++ b/tree-sitter/src/test/java/org/treesitter/TSQueryPredicateTest.java @@ -129,4 +129,21 @@ void predicateWithMultiByteChars() { assertTrue(cursor.nextMatch(match), "Should match '世界' using partial regex"); assertFalse(cursor.nextMatch(match)); } + + @Test + void testMissingSourceBytesThrowsException() { + // [1, null] + // Use a simple query that matches something but doesn't have its own predicates + query = new TSQuery(json, "((number) @val)"); + cursor.exec(query, rootNode, JSON_SRC); + TSQueryMatch match = new TSQueryMatch(); + assertTrue(cursor.nextMatch(match)); + + // Manually create a predicate to test the exception throwing behavior. + // Capture index 0 is '@val' from the query above. + TSQueryPredicate predicate = new TSQueryPredicate.TSQueryPredicateEq("eq?", 0, "1", -1, false); + + // Attempting to test predicates with null sourceBytes should throw IllegalStateException + assertThrows(IllegalStateException.class, () -> predicate.test(match, (byte[]) null)); + } } From 4fe85ab838dc7a8816dcc630dd217654178876c4 Mon Sep 17 00:00:00 2001 From: David Baker Effendi Date: Fri, 20 Mar 2026 09:54:59 +0200 Subject: [PATCH 2/4] test: add integration query test to TreeSitterRustTest --- .../org/treesitter/TreeSitterRustTest.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tree-sitter-rust/src/test/java/org/treesitter/TreeSitterRustTest.java b/tree-sitter-rust/src/test/java/org/treesitter/TreeSitterRustTest.java index 6f03015..7390b3c 100644 --- a/tree-sitter-rust/src/test/java/org/treesitter/TreeSitterRustTest.java +++ b/tree-sitter-rust/src/test/java/org/treesitter/TreeSitterRustTest.java @@ -5,9 +5,56 @@ import java.io.IOException; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + class TreeSitterRustTest { @Test void corpusTest() throws IOException { CorpusTest.runAllTestsInDefaultFolder(new TreeSitterRust(), "rust"); } + + @Test + void testIntegrationQuery() { + TSParser parser = new TSParser(); + parser.setLanguage(new TreeSitterRust()); + String source = "#[derive(Is)] \n" + + "pub enum Status {\n" + + " Running,\n" + + " Stopped,\n" + + " Initial,\n" + + "}"; + TSTree tree = parser.parseString(null, source); + TSNode rootNode = tree.getRootNode(); + + String queryString = "(attribute_item\n" + + " (attribute\n" + + " (identifier) @_derive\n" + + " (#eq? @_derive \"derive\")\n" + + " (token_tree\n" + + " (identifier) @macro.derive.name\n" + + " )\n" + + " )\n" + + ") @macro.derive"; + + TSQuery query = new TSQuery(parser.getLanguage(), queryString); + TSQueryCursor cursor = new TSQueryCursor(); + cursor.exec(query, rootNode, source); + + TSQueryMatch match = new TSQueryMatch(); + assertTrue(cursor.nextMatch(match), "Query should have matched"); + + boolean foundMacroDerive = false; + for (TSQueryCapture capture : match.getCaptures()) { + String captureName = query.getCaptureNameForId(capture.getIndex()); + if ("macro.derive".equals(captureName)) { + foundMacroDerive = true; + TSNode node = capture.getNode(); + String matchedText = source.substring(node.getStartByte(), node.getEndByte()); + assertTrue(matchedText.contains("#[derive(Is)]"), + "Matched text should contain the attribute. Found: " + matchedText); + } + } + assertTrue(foundMacroDerive, "Should have found @macro.derive capture"); + } } From 385f6279e7f85b9a074374e2960e82d74e0d805c Mon Sep 17 00:00:00 2001 From: David Baker Effendi Date: Fri, 20 Mar 2026 10:08:26 +0200 Subject: [PATCH 3/4] test: expand TreeSitterRustTest with macro and not-eq? query tests --- .../org/treesitter/TreeSitterRustTest.java | 62 ++++++++++++++++++- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/tree-sitter-rust/src/test/java/org/treesitter/TreeSitterRustTest.java b/tree-sitter-rust/src/test/java/org/treesitter/TreeSitterRustTest.java index 7390b3c..f3f781e 100644 --- a/tree-sitter-rust/src/test/java/org/treesitter/TreeSitterRustTest.java +++ b/tree-sitter-rust/src/test/java/org/treesitter/TreeSitterRustTest.java @@ -45,16 +45,72 @@ void testIntegrationQuery() { assertTrue(cursor.nextMatch(match), "Query should have matched"); boolean foundMacroDerive = false; + boolean foundDeriveName = false; for (TSQueryCapture capture : match.getCaptures()) { String captureName = query.getCaptureNameForId(capture.getIndex()); + TSNode node = capture.getNode(); + String matchedText = source.substring(node.getStartByte(), node.getEndByte()); + if ("macro.derive".equals(captureName)) { foundMacroDerive = true; - TSNode node = capture.getNode(); - String matchedText = source.substring(node.getStartByte(), node.getEndByte()); - assertTrue(matchedText.contains("#[derive(Is)]"), + assertTrue(matchedText.contains("#[derive(Is)]"), "Matched text should contain the attribute. Found: " + matchedText); + } else if ("macro.derive.name".equals(captureName)) { + foundDeriveName = true; + assertEquals("Is", matchedText, "macro.derive.name should match 'Is'"); } } assertTrue(foundMacroDerive, "Should have found @macro.derive capture"); + assertTrue(foundDeriveName, "Should have found @macro.derive.name capture"); + } + + @Test + void testIntegrationQueryNotEq() { + TSParser parser = new TSParser(); + parser.setLanguage(new TreeSitterRust()); + // Note: Corrected snippet from prompt (removed extra ')') + String source = "#[is_macro::Is]\n" + + "pub enum Status {\n" + + " Running,\n" + + " Stopped,\n" + + " Initial,\n" + + "}"; + TSTree tree = parser.parseString(null, source); + TSNode rootNode = tree.getRootNode(); + + String queryString = "(attribute_item\n" + + " (attribute\n" + + " [\n" + + " ((identifier) @macro.attribute.name\n" + + " (#not-eq? @macro.attribute.name \"derive\"))\n" + + " (scoped_identifier) @macro.attribute.name\n" + + " ]\n" + + " )\n" + + ") @macro.attribute"; + + TSQuery query = new TSQuery(parser.getLanguage(), queryString); + TSQueryCursor cursor = new TSQueryCursor(); + cursor.exec(query, rootNode, source); + + TSQueryMatch match = new TSQueryMatch(); + assertTrue(cursor.nextMatch(match), "Query should have matched is_macro::Is"); + + boolean foundAttribute = false; + boolean foundName = false; + for (TSQueryCapture capture : match.getCaptures()) { + String captureName = query.getCaptureNameForId(capture.getIndex()); + TSNode node = capture.getNode(); + String matchedText = source.substring(node.getStartByte(), node.getEndByte()); + + if ("macro.attribute".equals(captureName)) { + foundAttribute = true; + assertTrue(matchedText.contains("#[is_macro::Is]")); + } else if ("macro.attribute.name".equals(captureName)) { + foundName = true; + assertEquals("is_macro::Is", matchedText); + } + } + assertTrue(foundAttribute, "Should have found @macro.attribute capture"); + assertTrue(foundName, "Should have found @macro.attribute.name capture"); } } From 7d61a47e137cbc624d42c63d0cdf418c8acd527f Mon Sep 17 00:00:00 2001 From: David Baker Effendi Date: Fri, 20 Mar 2026 22:01:43 +0200 Subject: [PATCH 4/4] test: update and rename TreeSitterRust query integration tests --- .../org/treesitter/TreeSitterRustTest.java | 73 ++++++++++++++++++- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/tree-sitter-rust/src/test/java/org/treesitter/TreeSitterRustTest.java b/tree-sitter-rust/src/test/java/org/treesitter/TreeSitterRustTest.java index f3f781e..3249a63 100644 --- a/tree-sitter-rust/src/test/java/org/treesitter/TreeSitterRustTest.java +++ b/tree-sitter-rust/src/test/java/org/treesitter/TreeSitterRustTest.java @@ -6,6 +6,7 @@ import java.io.IOException; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; class TreeSitterRustTest { @@ -65,16 +66,84 @@ void testIntegrationQuery() { } @Test - void testIntegrationQueryNotEq() { + void testIntegrationQueryNotEqShouldNotMatch() { TSParser parser = new TSParser(); parser.setLanguage(new TreeSitterRust()); - // Note: Corrected snippet from prompt (removed extra ')') + + String source = "#[derive(Other)] \n" + + "pub enum Status {\n" + + " Running,\n" + + " Stopped,\n" + + " Initial,\n" + + "}"; + + TSTree tree = parser.parseString(null, source); + TSNode rootNode = tree.getRootNode(); + + String queryString = "(attribute_item\n" + + " (attribute\n" + + " (identifier) @_derive\n" + + " (#not-eq? @_derive \"derive\")\n" + + " (token_tree\n" + + " (identifier) @macro.derive.name\n" + + " )\n" + + " )\n" + + ") @macro.derive"; + + TSQuery query = new TSQuery(parser.getLanguage(), queryString); + TSQueryCursor cursor = new TSQueryCursor(); + cursor.exec(query, rootNode, source); + + TSQueryMatch match = new TSQueryMatch(); + assertFalse(cursor.nextMatch(match), "Query should not match because #not-eq? predicate fails"); + } + + @Test + void testIntegrationQueryEqShouldNotMatch() { + TSParser parser = new TSParser(); + parser.setLanguage(new TreeSitterRust()); + + String source = "#[foo(Other)] \n" + + "pub enum Status {\n" + + " Running,\n" + + " Stopped,\n" + + " Initial,\n" + + "}"; + + TSTree tree = parser.parseString(null, source); + TSNode rootNode = tree.getRootNode(); + + String queryString = "(attribute_item\n" + + " (attribute\n" + + " (identifier) @_derive\n" + + " (#eq? @_derive \"derive\")\n" + + " (token_tree\n" + + " (identifier) @macro.derive.name\n" + + " )\n" + + " )\n" + + ") @macro.derive"; + + TSQuery query = new TSQuery(parser.getLanguage(), queryString); + TSQueryCursor cursor = new TSQueryCursor(); + cursor.exec(query, rootNode, source); + + TSQueryMatch match = new TSQueryMatch(); + assertFalse(cursor.nextMatch(match), "Query should not match because #eq? predicate fails"); + } + + + @Test + void testIntegrationQueryNotEqShouldMatch() { + TSParser parser = new TSParser(); + parser.setLanguage(new TreeSitterRust()); + String source = "#[is_macro::Is]\n" + "pub enum Status {\n" + " Running,\n" + " Stopped,\n" + " Initial,\n" + "}"; + TSTree tree = parser.parseString(null, source); TSNode rootNode = tree.getRootNode();