@@ -24,10 +24,6 @@ pub struct PlPgSqlCheckDiagnostic {
24
24
#[ derive( Debug , Clone ) ]
25
25
pub struct PlPgSqlCheckAdvices {
26
26
pub code : Option < String > ,
27
- pub statement : Option < String > ,
28
- pub query : Option < String > ,
29
- pub line_number : Option < String > ,
30
- pub query_position : Option < String > ,
31
27
}
32
28
33
29
impl Advices for PlPgSqlCheckAdvices {
@@ -40,30 +36,6 @@ impl Advices for PlPgSqlCheckAdvices {
40
36
) ?;
41
37
}
42
38
43
- // Show statement information if available
44
- if let Some ( statement) = & self . statement {
45
- if let Some ( line_number) = & self . line_number {
46
- visitor. record_log (
47
- LogCategory :: Info ,
48
- & markup ! { "At line " <Emphasis >{ line_number} </Emphasis > ": " { statement} "" } ,
49
- ) ?;
50
- } else {
51
- visitor. record_log ( LogCategory :: Info , & markup ! { "Statement: " { statement} "" } ) ?;
52
- }
53
- }
54
-
55
- // Show query information if available
56
- if let Some ( query) = & self . query {
57
- if let Some ( pos) = & self . query_position {
58
- visitor. record_log (
59
- LogCategory :: Info ,
60
- & markup ! { "In query at position " <Emphasis >{ pos} </Emphasis > ":\n " { query} "" } ,
61
- ) ?;
62
- } else {
63
- visitor. record_log ( LogCategory :: Info , & markup ! { "Query:\n " { query} "" } ) ?;
64
- }
65
- }
66
-
67
39
Ok ( ( ) )
68
40
}
69
41
}
@@ -72,19 +44,19 @@ impl Advices for PlPgSqlCheckAdvices {
72
44
pub fn create_diagnostics_from_check_result (
73
45
result : & PlpgSqlCheckResult ,
74
46
fn_body : & str ,
75
- start : usize ,
47
+ offset : usize ,
76
48
) -> Vec < PlPgSqlCheckDiagnostic > {
77
49
result
78
50
. issues
79
51
. iter ( )
80
- . map ( |issue| create_diagnostic_from_issue ( issue, fn_body, start ) )
52
+ . map ( |issue| create_diagnostic_from_issue ( issue, fn_body, offset ) )
81
53
. collect ( )
82
54
}
83
55
84
56
fn create_diagnostic_from_issue (
85
57
issue : & PlpgSqlCheckIssue ,
86
58
fn_body : & str ,
87
- start : usize ,
59
+ offset : usize ,
88
60
) -> PlPgSqlCheckDiagnostic {
89
61
let severity = match issue. level . as_str ( ) {
90
62
"error" => Severity :: Error ,
@@ -93,50 +65,118 @@ fn create_diagnostic_from_issue(
93
65
_ => Severity :: Information ,
94
66
} ;
95
67
96
- let span = if let Some ( s) = & issue. statement {
97
- let line_number = s. line_number . parse :: < usize > ( ) . unwrap_or ( 0 ) ;
98
- if line_number > 0 {
99
- let mut current_offset = 0 ;
100
- let mut result = None ;
101
- for ( i, line) in fn_body. lines ( ) . enumerate ( ) {
102
- if i + 1 == line_number {
103
- if let Some ( stmt_pos) = line. to_lowercase ( ) . find ( & s. text . to_lowercase ( ) ) {
104
- let line_start = start + current_offset + stmt_pos;
105
- let line_end = line_start + s. text . len ( ) ;
106
- result = Some ( TextRange :: new (
107
- ( line_start as u32 ) . into ( ) ,
108
- ( line_end as u32 ) . into ( ) ,
109
- ) ) ;
110
- } else {
111
- let line_start = start + current_offset;
112
- let line_end = line_start + line. len ( ) ;
113
- result = Some ( TextRange :: new (
114
- ( line_start as u32 ) . into ( ) ,
115
- ( line_end as u32 ) . into ( ) ,
116
- ) ) ;
117
- }
118
- break ;
119
- }
120
- current_offset += line. len ( ) + 1 ;
121
- }
122
- result
123
- } else {
124
- None
125
- }
126
- } else {
127
- None
128
- } ;
129
-
130
68
PlPgSqlCheckDiagnostic {
131
69
message : issue. message . clone ( ) . into ( ) ,
132
70
severity,
133
- span,
71
+ span : resolve_span ( issue , fn_body , offset ) ,
134
72
advices : PlPgSqlCheckAdvices {
135
73
code : issue. sql_state . clone ( ) ,
136
- statement : issue. statement . as_ref ( ) . map ( |s| s. text . clone ( ) ) ,
137
- query : issue. query . as_ref ( ) . map ( |q| q. text . clone ( ) ) ,
138
- line_number : issue. statement . as_ref ( ) . map ( |s| s. line_number . clone ( ) ) ,
139
- query_position : issue. query . as_ref ( ) . map ( |q| q. position . clone ( ) ) ,
140
74
} ,
141
75
}
142
76
}
77
+
78
+ fn resolve_span ( issue : & PlpgSqlCheckIssue , fn_body : & str , offset : usize ) -> Option < TextRange > {
79
+ let stmt = issue. statement . as_ref ( ) ?;
80
+
81
+ let line_number = stmt
82
+ . line_number
83
+ . parse :: < usize > ( )
84
+ . expect ( "Expected line number to be a valid usize" ) ;
85
+
86
+ let text = & stmt. text ;
87
+
88
+ // calculate the offset to the target line
89
+ let line_offset: usize = fn_body
90
+ . lines ( )
91
+ . take ( line_number - 1 )
92
+ . map ( |line| line. len ( ) + 1 ) // +1 for newline
93
+ . sum ( ) ;
94
+
95
+ // find the position within the target line
96
+ let line = fn_body. lines ( ) . nth ( line_number - 1 ) ?;
97
+ let start = line
98
+ . to_lowercase ( )
99
+ . find ( & text. to_lowercase ( ) )
100
+ . unwrap_or_else ( || {
101
+ line. char_indices ( )
102
+ . find_map ( |( i, c) | if !c. is_whitespace ( ) { Some ( i) } else { None } )
103
+ . unwrap_or ( 0 )
104
+ } ) ;
105
+
106
+ let stmt_offset = line_offset + start;
107
+
108
+ if let Some ( q) = & issue. query {
109
+ // first find the query within the fn body *after* stmt_offset
110
+ let query_start = fn_body[ stmt_offset..]
111
+ . to_lowercase ( )
112
+ . find ( & q. text . to_lowercase ( ) )
113
+ . map ( |pos| pos + stmt_offset) ;
114
+
115
+ // the position is *within* the query text
116
+ let pos = q
117
+ . position
118
+ . parse :: < usize > ( )
119
+ . expect ( "Expected query position to be a valid usize" )
120
+ - 1 ; // -1 because the position is 1-based
121
+
122
+ let start = query_start? + pos;
123
+
124
+ // the range of the diagnostics is the token that `pos` is on
125
+ // Find the end of the current token by looking for whitespace or SQL delimiters
126
+ let remaining = & fn_body[ start..] ;
127
+ let end = remaining
128
+ . char_indices ( )
129
+ . find ( |( _, c) | {
130
+ c. is_whitespace ( ) || matches ! ( c, ',' | ';' | ')' | '(' | '=' | '<' | '>' )
131
+ } )
132
+ . map ( |( i, c) | {
133
+ if matches ! ( c, ';' ) {
134
+ i + 1 // include the semicolon
135
+ } else {
136
+ i // just the token end
137
+ }
138
+ } )
139
+ . unwrap_or ( remaining. len ( ) ) ;
140
+
141
+ return Some ( TextRange :: new (
142
+ ( ( offset + start) as u32 ) . into ( ) ,
143
+ ( ( offset + start + end) as u32 ) . into ( ) ,
144
+ ) ) ;
145
+ }
146
+
147
+ // if no query is present, the end range covers
148
+ // - if text is "IF" or "ELSIF", then until the next "THEN"
149
+ // - TODO: check "LOOP", "CASE", "WHILE", "EXPECTION" and others
150
+ // - else: until the next semicolon or end of line
151
+
152
+ if text. to_uppercase ( ) == "IF" || text. to_uppercase ( ) == "ELSIF" {
153
+ // Find the position of the next "THEN" after the statement
154
+ let remaining = & fn_body[ stmt_offset..] ;
155
+ if let Some ( then_pos) = remaining. to_uppercase ( ) . find ( "THEN" ) {
156
+ let end = then_pos + "THEN" . len ( ) ;
157
+ return Some ( TextRange :: new (
158
+ ( ( offset + stmt_offset) as u32 ) . into ( ) ,
159
+ ( ( offset + stmt_offset + end) as u32 ) . into ( ) ,
160
+ ) ) ;
161
+ }
162
+ }
163
+
164
+ // if no specific end is found, use the next semicolon or the end of the line
165
+ let remaining = & fn_body[ stmt_offset..] ;
166
+ let end = remaining
167
+ . char_indices ( )
168
+ . find ( |( _, c) | matches ! ( c, ';' | '\n' | '\r' ) )
169
+ . map ( |( i, c) | {
170
+ if c == ';' {
171
+ i + 1 // include the semicolon
172
+ } else {
173
+ i // just the end of the line
174
+ }
175
+ } )
176
+ . unwrap_or ( remaining. len ( ) ) ;
177
+
178
+ Some ( TextRange :: new (
179
+ ( ( offset + stmt_offset) as u32 ) . into ( ) ,
180
+ ( ( offset + stmt_offset + end) as u32 ) . into ( ) ,
181
+ ) )
182
+ }
0 commit comments