diff --git a/src/runtime/get_changed_ranges.c b/src/runtime/get_changed_ranges.c
index 578f0059..900d36ed 100644
--- a/src/runtime/get_changed_ranges.c
+++ b/src/runtime/get_changed_ranges.c
@@ -445,6 +445,14 @@ unsigned ts_subtree_get_changed_ranges(const Subtree *old_tree, const Subtree *n
}
} while (!iterator_done(&old_iter) && !iterator_done(&new_iter));
+ Length old_size = ts_subtree_total_size(*old_tree);
+ Length new_size = ts_subtree_total_size(*new_tree);
+ if (old_size.bytes < new_size.bytes) {
+ ts_range_array_add(&results, old_size, new_size);
+ } else if (new_size.bytes < old_size.bytes) {
+ ts_range_array_add(&results, new_size, old_size);
+ }
+
*cursor1 = old_iter.cursor;
*cursor2 = new_iter.cursor;
*ranges = results.contents;
diff --git a/test/runtime/parser_test.cc b/test/runtime/parser_test.cc
index 2d154637..cb168234 100644
--- a/test/runtime/parser_test.cc
+++ b/test/runtime/parser_test.cc
@@ -1004,7 +1004,7 @@ describe("Parser", [&]() {
AssertThat(ts_node_end_point(statement_node2), Equals(extent_for_string("a <%= b() %> c <% d()")));
});
- it("does not reuse nodes that were parsed in ranges that are now excluded", [&]() {
+ it("handles syntax changes in ranges that were included but are now excluded", [&]() {
string source_code = "
<%= something %>
";
// Parse HTML including the template directive, which will cause an error
@@ -1045,7 +1045,8 @@ describe("Parser", [&]() {
ts_parser_set_included_ranges(parser, included_ranges, 2);
tree = ts_parser_parse_string(parser, first_tree, source_code.c_str(), source_code.size());
- // The error should not have been reused, because the included ranges were different.
+ // The element node (which contained an error) should not be reused,
+ // because it contains a range which is now excluded.
assert_root_node("(fragment "
"(text) "
"(element "
@@ -1077,6 +1078,55 @@ describe("Parser", [&]() {
ts_free((void *)ranges);
ts_tree_delete(first_tree);
});
+
+ it("handles syntax changes in ranges that were excluded but are now included", [&]() {
+ ts_parser_set_language(parser, load_real_language("javascript"));
+
+ string source_code = "<%= foo() %>
<%= bar() %>";
+
+ unsigned first_code_start_index = source_code.find(" foo");
+ unsigned first_code_end_index = first_code_start_index + 7;
+ unsigned second_code_start_index = source_code.find(" bar");
+ unsigned second_code_end_index = second_code_start_index + 7;
+
+ TSRange included_ranges[] = {
+ {
+ {0, first_code_start_index},
+ {0, first_code_end_index},
+ first_code_start_index,
+ first_code_end_index
+ },
+ {
+ {0, second_code_start_index},
+ {0, second_code_end_index},
+ second_code_start_index,
+ second_code_end_index
+ },
+ };
+
+ // Parse only the first code directive as JavaScript
+ ts_parser_set_included_ranges(parser, included_ranges, 1);
+ TSTree *first_tree = ts_parser_parse_string(parser, nullptr, source_code.c_str(), source_code.size());
+
+ // Parse both the code directives as JavaScript, using the old tree as a reference.
+ ts_parser_set_included_ranges(parser, included_ranges, 2);
+ tree = ts_parser_parse_string(parser, first_tree, source_code.c_str(), source_code.size());
+
+ assert_root_node("(program "
+ "(expression_statement (call_expression (identifier) (arguments))) "
+ "(expression_statement (call_expression (identifier) (arguments))))");
+
+ unsigned range_count;
+ const TSRange *ranges = ts_tree_get_changed_ranges(first_tree, tree, &range_count);
+ AssertThat(range_count, Equals(1u));
+ AssertThat(ranges[0], Equals({
+ {0, first_code_end_index + 1}, {0, second_code_end_index + 1},
+ first_code_end_index + 1, second_code_end_index + 1,
+ }));
+
+ ts_free((void *)ranges);
+ ts_tree_delete(first_tree);
+ });
});
describe("ts_range_array_get_changed_ranges()", [&]() {