Skip to content

Commit

Permalink
Add outermost enclosing node semantics (#517)
Browse files Browse the repository at this point in the history
  • Loading branch information
ketkarameya committed Jun 23, 2023
1 parent ff8a6d5 commit 4ac0fda
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 9 deletions.
61 changes: 55 additions & 6 deletions src/models/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use std::collections::HashMap;
use derive_builder::Builder;
use getset::Getters;
use itertools::Itertools;

use pyo3::prelude::{pyclass, pymethods};

use serde_derive::Deserialize;
Expand Down Expand Up @@ -43,11 +44,21 @@ use super::default_configs::{
#[builder(build_fn(name = "create"))]
pub struct Filter {
/// AST patterns that some ancestor node of the primary match should match
/// In case of multiple ancestors matching the AST pattern it will consider the innermost ancestor that matches.
#[builder(default = "default_enclosing_node()")]
#[get = "pub"]
#[serde(default = "default_enclosing_node")]
#[pyo3(get)]
enclosing_node: TSQuery,

/// AST patterns that some ancestor node of the primary match should match
/// In case of multiple ancestors matching the AST pattern it will consider the outermost ancestor that matches.
#[builder(default = "default_enclosing_node()")]
#[get = "pub"]
#[serde(default = "default_enclosing_node")]
#[pyo3(get)]
outermost_enclosing_node: TSQuery,

/// AST patterns NO ancestor node of the primary match should match
#[builder(default = "default_not_enclosing_node()")]
#[get = "pub"]
Expand Down Expand Up @@ -98,12 +109,14 @@ pub struct Filter {
impl Filter {
#[new]
fn py_new(
enclosing_node: Option<String>, not_enclosing_node: Option<String>,
not_contains: Option<Vec<String>>, contains: Option<String>, at_least: Option<u32>,
at_most: Option<u32>, child_count: Option<u32>, sibling_count: Option<u32>,
enclosing_node: Option<String>, outermost_enclosing_node: Option<String>,
not_enclosing_node: Option<String>, not_contains: Option<Vec<String>>,
contains: Option<String>, at_least: Option<u32>, at_most: Option<u32>,
child_count: Option<u32>, sibling_count: Option<u32>,
) -> Self {
FilterBuilder::default()
.enclosing_node(TSQuery::new(enclosing_node.unwrap_or_default()))
.outermost_enclosing_node(TSQuery::new(outermost_enclosing_node.unwrap_or_default()))
.not_enclosing_node(TSQuery::new(not_enclosing_node.unwrap_or_default()))
.not_contains(
not_contains
Expand Down Expand Up @@ -155,6 +168,10 @@ impl Validator for Filter {
self.enclosing_node().validate()?
}

if *self.outermost_enclosing_node() != default_enclosing_node() {
self.outermost_enclosing_node().validate()?
}

if *self.not_enclosing_node() != default_not_enclosing_node() {
self.not_enclosing_node().validate()?
}
Expand All @@ -171,6 +188,7 @@ impl Validator for Filter {
|| *self.sibling_count() != default_sibling_count())
&& (*self.enclosing_node() != default_enclosing_node()
|| *self.not_enclosing_node() != default_not_enclosing_node()
|| *self.outermost_enclosing_node() != default_enclosing_node()
|| *self.contains() != default_contains_query()
|| *self.not_contains() != default_not_contains_queries())
{
Expand Down Expand Up @@ -239,9 +257,10 @@ impl FilterBuilder {
/// ```
///
macro_rules! filter {
($(enclosing_node = $enclosing_node:expr)? $(, not_enclosing_node=$not_enclosing_node:expr)? $(, not_contains= [$($q:expr,)*])? $(, contains= $p:expr)? $(, at_least=$min:expr)? $(, at_most=$max:expr)? $(, child_count=$nChildren:expr)? $(, sibling_count=$nSibling:expr)?) => {
($(enclosing_node = $enclosing_node:expr)? $(, outermost_enclosing_node=$outermost_enclosing_node:expr)? $(, not_enclosing_node=$not_enclosing_node:expr)? $(, not_contains= [$($q:expr,)*])? $(, contains= $p:expr)? $(, at_least=$min:expr)? $(, at_most=$max:expr)? $(, child_count=$nChildren:expr)? $(, sibling_count=$nSibling:expr)?) => {
$crate::models::filter::FilterBuilder::default()
$(.enclosing_node($crate::utilities::tree_sitter_utilities::TSQuery::new($enclosing_node.to_string())))?
$(.outermost_enclosing_node($crate::utilities::tree_sitter_utilities::TSQuery::new($outermost_enclosing_node.to_string())))?
$(.not_enclosing_node($crate::utilities::tree_sitter_utilities::TSQuery::new($not_enclosing_node.to_string())))?
$(.not_contains(vec![$($crate::utilities::tree_sitter_utilities::TSQuery::new($q.to_string()),)*]))?
$(.contains($crate::utilities::tree_sitter_utilities::TSQuery::new($p.to_string())))?
Expand All @@ -260,6 +279,9 @@ impl Instantiate for Filter {
fn instantiate(&self, substitutions_for_holes: &HashMap<String, String>) -> Filter {
Filter {
enclosing_node: self.enclosing_node().instantiate(substitutions_for_holes),
outermost_enclosing_node: self
.outermost_enclosing_node()
.instantiate(substitutions_for_holes),
not_enclosing_node: self
.not_enclosing_node()
.instantiate(substitutions_for_holes),
Expand Down Expand Up @@ -295,7 +317,7 @@ impl SourceCodeUnit {
///
/// The `filter` is composed of:
/// (i) `enclosing_node`, the node to inspect, optional. If not provided we check whether the contains or non_contains are satisfied in the current node.
/// (ii) `not_enclosing_node`, optionalquery that no ancestor of the primary match should match,
/// (ii) `not_enclosing_node`, optional query that no ancestor of the primary match should match,
/// (iii) `not_contains` and `contains`, optional queries that should not and should match within the `enclosing_node`,
/// (iv) `at_least` and `at_most`, optional parameters indicating the acceptable range of matches for `contains` within the `enclosing_node`.
///
Expand Down Expand Up @@ -333,6 +355,16 @@ impl SourceCodeUnit {
}
}

// If an outermost enclosing node is provided
let query = instantiated_filter.outermost_enclosing_node();
if !query.get_query().is_empty() {
if let Some(result) = self._match_outermost_ancestor(rule_store, node_to_check, query) {
node_to_check = result;
} else {
return false;
}
}

self._check_filter_not_contains(&instantiated_filter, rule_store, &node_to_check)
&& self._check_filter_contains(&instantiated_filter, rule_store, &node_to_check)
}
Expand All @@ -354,7 +386,24 @@ impl SourceCodeUnit {
true
}

/// Search for any ancestor of `node` (including itself) that matches `query_str`
/// Search for outermost ancestor of `node` (including itself) that matches `query_str`
fn _match_outermost_ancestor(
&self, rule_store: &mut RuleStore, node: Node, ts_query: &TSQuery,
) -> Option<Node> {
let mut matched_ancestor = self._match_ancestor(rule_store, node, ts_query);
loop {
if let Some(outer_matched_ancestor) = matched_ancestor
.and_then(|m| m.parent().filter(|p| p.range() != m.range()))
.and_then(|parent| self._match_ancestor(rule_store, parent, ts_query))
{
matched_ancestor = Some(outer_matched_ancestor);
continue;
}
return matched_ancestor;
}
}

/// Search for innermost ancestor of `node` (including itself) that matches `query_str`
fn _match_ancestor(
&self, rule_store: &mut RuleStore, node: Node, ts_query: &TSQuery,
) -> Option<Node> {
Expand Down
68 changes: 68 additions & 0 deletions src/models/unit_tests/source_code_unit_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -631,3 +631,71 @@ fn test_contains_no_enclosing_positive() {
|result| result,
);
}

#[test]
fn test_satisfies_outermost_enclosing_node() {
let rule_positive = piranha_rule! {
name= "test",
query= "(
(method_declaration name: (_) @name) @md
(#eq? @name \"foobar\")
)",
filters= [filter!{
, outermost_enclosing_node = "(class_declaration) @cd"
, contains = "((method_invocation name: (_) @mname) @mi (#eq? @mname \"foobar\"))"
}]
};
let rule_positive = InstantiatedRule::new(&rule_positive, &HashMap::new());

let rule_negative = piranha_rule! {
name= "test",
query= "(
(method_declaration name: (_) @name) @md
(#eq? @name \"foobar\")
)",
filters= [filter!{
, outermost_enclosing_node = "(class_declaration) @cd"
, not_contains = ["((method_invocation name: (_) @mname) @mi (#eq? @mname \"foobar\"))",]
}]
};
let rule_negative = InstantiatedRule::new(&rule_negative, &HashMap::new());

let source_code = "class OuterClass {
void someMethod() {
Test t = new Test();
t.foobar();
}
class MiddleClass {
class Test {
private void foobar(){
System.out.println();
}
}
}
}";

let mut rule_store = RuleStore::default();
let java = get_java_tree_sitter_language();
let mut parser = java.parser();
let piranha_arguments = &PiranhaArgumentsBuilder::default()
.path_to_codebase(UNUSED_CODE_PATH.to_string())
.language(java)
.build();
let source_code_unit = SourceCodeUnit::new(
&mut parser,
source_code.to_string(),
&HashMap::new(),
PathBuf::new().as_path(),
piranha_arguments,
);

let node = &source_code_unit
.root_node()
.descendant_for_byte_range(119, 178)
.unwrap();

assert!(!source_code_unit.is_satisfied(*node, &rule_negative, &HashMap::new(), &mut rule_store,));

assert!(source_code_unit.is_satisfied(*node, &rule_positive, &HashMap::new(), &mut rule_store,));
}
10 changes: 7 additions & 3 deletions src/tests/test_piranha_swift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,6 @@ create_rewrite_tests! {
"stale_flag" => "one"
},
cleanup_comments = true, delete_file_if_empty= false;

test_cleanup_with_derivatives: "cleanup_with_derivatives", 1,
cleanup_comments = true, delete_file_if_empty= false;
}

fn execute_piranha_with_default_swift_args(scenario: &str, substitutions: Vec<(String, String)>) {
Expand Down Expand Up @@ -75,6 +72,13 @@ fn test_cleanup_rules_file() {
);
}

#[test]
#[ignore] // Long running test
fn test_cleanup_with_derivatives() {
super::initialize();
execute_piranha_with_default_swift_args("cleanup_with_derivatives", vec![]);
}

#[test]
#[ignore] // Long running test
fn test_local_variable_inline_file() {
Expand Down

0 comments on commit 4ac0fda

Please sign in to comment.