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 ;
811use ruff_python_ast:: StringFlags ;
12+ use ruff_python_ast:: visitor:: Visitor ;
13+ use ruff_python_ast:: visitor:: walk_expr;
914use ruff_python_parser:: Mode ;
1015use ruff_python_parser:: ParseOptions ;
11- use ruff_python_parser:: Token ;
1216use ruff_python_parser:: TokenKind ;
1317use ruff_python_parser:: parse_unchecked;
1418use 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