Skip to content

Commit 4df70f4

Browse files
[ty] Fix folding ranges of comments separated by statements (#24132)
Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
1 parent 8e26042 commit 4df70f4

1 file changed

Lines changed: 119 additions & 4 deletions

File tree

crates/ty_ide/src/folding_range.rs

Lines changed: 119 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use ruff_python_ast::visitor::source_order::{
66
SourceOrderVisitor, TraversalSignal, walk_body, walk_node,
77
};
88
use ruff_python_ast::{AnyNodeRef, Stmt, StmtClassDef, StmtFunctionDef};
9-
use ruff_python_trivia::CommentLinePosition;
9+
use ruff_python_trivia::{CommentLinePosition, is_python_whitespace};
1010
use ruff_source_file::{LineRanges, UniversalNewlines};
1111
use ruff_text_size::{Ranged, TextRange, TextSize};
1212

@@ -190,7 +190,7 @@ impl FoldingRangeVisitor<'_> {
190190

191191
/// Check if there's a blank line appearing anywhere between two positions.
192192
fn has_blank_line_between(&self, start: TextSize, end: TextSize) -> bool {
193-
let mut count = 0;
193+
let mut count = 0usize;
194194
for line in self.source[TextRange::new(start, end)].universal_newlines() {
195195
if !line.is_empty() {
196196
return count >= 2;
@@ -244,9 +244,18 @@ impl FoldingRangeVisitor<'_> {
244244
!text.starts_with("region") && !text.starts_with("endregion");
245245

246246
if is_non_region_comment {
247-
// Extend the current comment block unless a blank line forces a new folding range.
247+
// Extend the current comment block unless a blank line or a statement separates the comments.
248248
if let Some(ref mut comment_block_range) = comment_block_range {
249-
if self.has_blank_line_between(comment_block_range.end(), comment_range.start())
249+
let has_text_between = !self.source
250+
[TextRange::new(comment_block_range.end(), comment_range.start())]
251+
.trim_matches(|c| matches!(c, '\n' | '\r') || is_python_whitespace(c))
252+
.is_empty();
253+
254+
if has_text_between
255+
|| self.has_blank_line_between(
256+
comment_block_range.end(),
257+
comment_range.start(),
258+
)
250259
{
251260
self.add_range(
252261
FoldingRange::from(*comment_block_range)
@@ -1758,6 +1767,112 @@ with open("file.txt") as f:
17581767
"#);
17591768
}
17601769

1770+
/// Regression test for <https://github.com/astral-sh/ty/issues/3106>
1771+
#[test]
1772+
fn folding_range_comment_block() {
1773+
let test = CursorTest::builder()
1774+
.source(
1775+
"main.py",
1776+
r#"
1777+
def chunk_date_range():
1778+
"""Split a date range into chunks respecting the maximum days limit.
1779+
1780+
The API has a 1-month limit, so this function splits larger ranges
1781+
into smaller chunks that can be requested individually.
1782+
"""
1783+
# Handle both date-only and datetime strings
1784+
a = 10
1785+
1786+
while current <= end:
1787+
# Calculate the end of the current chunk
1788+
# Go to the last day of the current month
1789+
b = 20
1790+
1791+
# Don't exceed the overall end date or max_days limit
1792+
c = 30
1793+
<CURSOR>
1794+
"#,
1795+
)
1796+
.build();
1797+
1798+
assert_snapshot!(test.folding_ranges(), @r#"
1799+
info[folding-range]: Folding Range
1800+
--> main.py:2:17
1801+
|
1802+
2 | / def chunk_date_range():
1803+
3 | | """Split a date range into chunks respecting the maximum days limit.
1804+
4 | |
1805+
5 | | The API has a 1-month limit, so this function splits larger ranges
1806+
6 | | into smaller chunks that can be requested individually.
1807+
7 | | """
1808+
8 | | # Handle both date-only and datetime strings
1809+
9 | | a = 10
1810+
10 | |
1811+
11 | | while current <= end:
1812+
12 | | # Calculate the end of the current chunk
1813+
13 | | # Go to the last day of the current month
1814+
14 | | b = 20
1815+
15 | |
1816+
16 | | # Don't exceed the overall end date or max_days limit
1817+
17 | | c = 30
1818+
| |______________________________^
1819+
|
1820+
1821+
info[folding-range]: Folding Range (comment)
1822+
--> main.py:3:21
1823+
|
1824+
2 | def chunk_date_range():
1825+
3 | / """Split a date range into chunks respecting the maximum days limit.
1826+
4 | |
1827+
5 | | The API has a 1-month limit, so this function splits larger ranges
1828+
6 | | into smaller chunks that can be requested individually.
1829+
7 | | """
1830+
| |_______________________^
1831+
8 | # Handle both date-only and datetime strings
1832+
9 | a = 10
1833+
|
1834+
1835+
info[folding-range]: Folding Range
1836+
--> main.py:3:21
1837+
|
1838+
2 | def chunk_date_range():
1839+
3 | / """Split a date range into chunks respecting the maximum days limit.
1840+
4 | |
1841+
5 | | The API has a 1-month limit, so this function splits larger ranges
1842+
6 | | into smaller chunks that can be requested individually.
1843+
7 | | """
1844+
| |_______________________^
1845+
8 | # Handle both date-only and datetime strings
1846+
9 | a = 10
1847+
|
1848+
1849+
info[folding-range]: Folding Range
1850+
--> main.py:11:21
1851+
|
1852+
9 | a = 10
1853+
10 |
1854+
11 | / while current <= end:
1855+
12 | | # Calculate the end of the current chunk
1856+
13 | | # Go to the last day of the current month
1857+
14 | | b = 20
1858+
15 | |
1859+
16 | | # Don't exceed the overall end date or max_days limit
1860+
17 | | c = 30
1861+
| |______________________________^
1862+
|
1863+
1864+
info[folding-range]: Folding Range (comment)
1865+
--> main.py:12:1
1866+
|
1867+
11 | while current <= end:
1868+
12 | / # Calculate the end of the current chunk
1869+
13 | | # Go to the last day of the current month
1870+
| |_________________________________________________________________^
1871+
14 | b = 20
1872+
|
1873+
"#);
1874+
}
1875+
17611876
#[test]
17621877
fn test_folding_multiline() {
17631878
// A class definition on a single line shouldn't have

0 commit comments

Comments
 (0)