Skip to content

Commit 8f9890d

Browse files
fix
1 parent 639dc4b commit 8f9890d

1 file changed

Lines changed: 64 additions & 24 deletions

File tree

pyrefly/lib/lsp/rename.rs

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8+
use ruff_python_ast::BytesLiteral;
9+
use ruff_python_ast::Expr;
10+
use ruff_python_ast::Mod;
811
use ruff_python_ast::StringFlags;
12+
use ruff_python_ast::visitor::Visitor;
13+
use ruff_python_ast::visitor::walk_expr;
914
use ruff_python_parser::Mode;
1015
use ruff_python_parser::ParseOptions;
11-
use ruff_python_parser::Token;
1216
use ruff_python_parser::TokenKind;
1317
use ruff_python_parser::parse_unchecked;
1418
use ruff_text_size::Ranged;
@@ -24,40 +28,72 @@ pub(crate) fn comment_and_string_content_ranges(source: &str) -> Vec<TextRange>
2428
let mut ranges = Vec::new();
2529

2630
for token in parsed.tokens() {
27-
match token.kind() {
28-
TokenKind::Comment => ranges.push(token.range()),
29-
TokenKind::String => {
30-
if let Some(range) = string_literal_content_range(token) {
31-
ranges.push(range);
32-
}
31+
if token.kind() == TokenKind::Comment {
32+
ranges.push(token.range());
33+
}
34+
}
35+
36+
let mut collector = StringContentRangeCollector::default();
37+
match parsed.syntax() {
38+
Mod::Module(module) => {
39+
for stmt in &module.body {
40+
collector.visit_stmt(stmt);
3341
}
34-
TokenKind::FStringMiddle | TokenKind::TStringMiddle => ranges.push(token.range()),
35-
_ => {}
3642
}
43+
Mod::Expression(expr) => collector.visit_expr(&expr.body),
3744
}
45+
ranges.extend(collector.ranges);
3846

3947
ranges
4048
}
4149

42-
fn string_literal_content_range(token: &Token) -> Option<TextRange> {
43-
let flags = token.string_flags()?;
44-
let prefix_len = flags.prefix().text_len();
45-
let quote_len = if flags.triple_quotes().is_yes() {
46-
TextSize::new(3)
47-
} else {
48-
TextSize::new(1)
49-
};
50-
51-
let start = token.range().start() + prefix_len + quote_len;
52-
let mut end = token.range().end();
53-
if !flags.is_unclosed() {
54-
end = end.saturating_sub(quote_len);
50+
#[derive(Default)]
51+
struct StringContentRangeCollector {
52+
ranges: Vec<TextRange>,
53+
}
54+
55+
impl<'a> Visitor<'a> for StringContentRangeCollector {
56+
fn visit_expr(&mut self, expr: &'a Expr) {
57+
match expr {
58+
Expr::StringLiteral(expr) => {
59+
for literal in expr.value.iter() {
60+
self.ranges.push(literal.content_range());
61+
}
62+
}
63+
Expr::BytesLiteral(expr) => {
64+
for literal in expr.value.iter() {
65+
if let Some(range) = bytes_literal_content_range(literal) {
66+
self.ranges.push(range);
67+
}
68+
}
69+
}
70+
Expr::FString(expr) => {
71+
for element in expr.value.elements() {
72+
if let Some(literal) = element.as_literal() {
73+
self.ranges.push(literal.range);
74+
}
75+
}
76+
}
77+
Expr::TString(expr) => {
78+
for element in expr.value.elements() {
79+
if let Some(literal) = element.as_literal() {
80+
self.ranges.push(literal.range);
81+
}
82+
}
83+
}
84+
_ => {}
85+
}
86+
87+
walk_expr(self, expr);
5588
}
89+
}
5690

91+
fn bytes_literal_content_range(literal: &BytesLiteral) -> Option<TextRange> {
92+
let start = literal.range.start() + literal.flags.opener_len();
93+
let end = literal.range.end().saturating_sub(literal.flags.closer_len());
5794
if start >= end {
5895
return None;
5996
}
60-
6197
Some(TextRange::new(start, end))
6298
}
6399

@@ -149,6 +185,10 @@ mod tests {
149185
let foo_hits = find_word_occurrences_in_ranges(source, "foo", &ranges);
150186

151187
assert!(r_hits.is_empty());
152-
assert!(foo_hits.is_empty());
188+
assert_eq!(foo_hits.len(), 1);
189+
let range = foo_hits[0];
190+
let start = range.start().to_usize();
191+
let end = range.end().to_usize();
192+
assert_eq!(&source[start..end], "foo");
153193
}
154194
}

0 commit comments

Comments
 (0)