diff --git a/cli/src/tests/node_test.rs b/cli/src/tests/node_test.rs index 4cf9fc13..74e123f4 100644 --- a/cli/src/tests/node_test.rs +++ b/cli/src/tests/node_test.rs @@ -1,8 +1,9 @@ use super::helpers::edits::get_random_edit; -use super::helpers::fixtures::{get_language, get_test_language}; +use super::helpers::fixtures::{fixtures_dir, get_language, get_test_language}; use super::helpers::random::Rand; use crate::generate::generate_parser_for_grammar; use crate::parse::perform_edit; +use std::fs; use tree_sitter::{Node, Parser, Point, Tree}; const JSON_EXAMPLE: &'static str = r#" @@ -595,6 +596,37 @@ fn test_node_field_calls_in_language_without_fields() { assert_eq!(cursor.field_name(), None); } +#[test] +fn test_node_is_named_but_aliased_as_anonymous() { + let (parser_name, parser_code) = generate_parser_for_grammar( + &fs::read_to_string( + &fixtures_dir() + .join("test_grammars") + .join("named_rule_aliased_as_anonymous") + .join("grammar.json"), + ) + .unwrap(), + ) + .unwrap(); + + let mut parser = Parser::new(); + let language = get_test_language(&parser_name, &parser_code, None); + parser.set_language(language).unwrap(); + + let tree = parser.parse("B C B", None).unwrap(); + + let root_node = tree.root_node(); + assert!(!root_node.has_error()); + assert_eq!(root_node.child_count(), 3); + assert_eq!(root_node.named_child_count(), 2); + + let aliased = root_node.child(0).unwrap(); + assert!(!aliased.is_named()); + assert_eq!(aliased.kind(), "the-alias"); + + assert_eq!(root_node.named_child(0).unwrap().kind(), "c"); +} + fn get_all_nodes(tree: &Tree) -> Vec { let mut result = Vec::new(); let mut visited_children = false; diff --git a/lib/src/node.c b/lib/src/node.c index 355585f2..6b2be36e 100644 --- a/lib/src/node.c +++ b/lib/src/node.c @@ -108,11 +108,12 @@ static inline bool ts_node__is_relevant(TSNode self, bool include_anonymous) { if (include_anonymous) { return ts_subtree_visible(tree) || ts_node__alias(&self); } else { - return - (ts_subtree_visible(tree) && - ts_subtree_named(tree)) || - (ts_node__alias(&self) && - ts_language_symbol_metadata(self.tree->language, ts_node__alias(&self)).named); + TSSymbol alias = ts_node__alias(&self); + if (alias) { + return ts_language_symbol_metadata(self.tree->language, alias).named; + } else { + return ts_subtree_visible(tree) && ts_subtree_named(tree); + } } } @@ -440,8 +441,9 @@ bool ts_node_is_extra(TSNode self) { } bool ts_node_is_named(TSNode self) { - return ts_node__alias(&self) - ? ts_language_symbol_metadata(self.tree->language, ts_node__alias(&self)).named + TSSymbol alias = ts_node__alias(&self); + return alias + ? ts_language_symbol_metadata(self.tree->language, alias).named : ts_subtree_named(ts_node__subtree(self)); } diff --git a/lib/src/subtree.c b/lib/src/subtree.c index 6a3a63df..e95733eb 100644 --- a/lib/src/subtree.c +++ b/lib/src/subtree.c @@ -817,9 +817,12 @@ static size_t ts_subtree__write_to_string( bool is_root = field_name == ROOT_FIELD; bool is_visible = include_all || - alias_is_named || ts_subtree_missing(self) || - (ts_subtree_visible(self) && ts_subtree_named(self)); + ( + alias_symbol + ? alias_is_named + : ts_subtree_visible(self) && ts_subtree_named(self) + ); if (is_visible) { if (!is_root) { diff --git a/test/fixtures/test_grammars/named_rule_aliased_as_anonymous/corpus.txt b/test/fixtures/test_grammars/named_rule_aliased_as_anonymous/corpus.txt new file mode 100644 index 00000000..6ccbe1ed --- /dev/null +++ b/test/fixtures/test_grammars/named_rule_aliased_as_anonymous/corpus.txt @@ -0,0 +1,9 @@ +================================================ +Named rules that are aliased as anonymous tokens +================================================ + +B C B + +--- + +(a (c) (b)) diff --git a/test/fixtures/test_grammars/named_rule_aliased_as_anonymous/grammar.json b/test/fixtures/test_grammars/named_rule_aliased_as_anonymous/grammar.json new file mode 100644 index 00000000..2ff80dde --- /dev/null +++ b/test/fixtures/test_grammars/named_rule_aliased_as_anonymous/grammar.json @@ -0,0 +1,33 @@ +{ + "name": "named_rule_aliased_as_anonymous", + + "extras": [ + {"type": "PATTERN", "value": "\\s"} + ], + + "rules": { + "a": { + "type": "SEQ", + "members": [ + { + "type": "ALIAS", + "value": "the-alias", + "named": false, + "content": {"type": "SYMBOL", "name": "b"} + }, + {"type": "SYMBOL", "name": "c"}, + {"type": "SYMBOL", "name": "b"} + ] + }, + + "b": { + "type": "STRING", + "value": "B" + }, + + "c": { + "type": "STRING", + "value": "C" + } + } +} diff --git a/test/fixtures/test_grammars/named_rule_aliased_as_anonymous/readme.md b/test/fixtures/test_grammars/named_rule_aliased_as_anonymous/readme.md new file mode 100644 index 00000000..185dfa2c --- /dev/null +++ b/test/fixtures/test_grammars/named_rule_aliased_as_anonymous/readme.md @@ -0,0 +1 @@ +This grammar checks that if a named node is aliased as an anonymous node (e.g. `alias($.foo, 'bar')`), then the rule will behave like an anonymous node. In particular, it will not show up in the tree's S-expression representation.