Find all references to a variable within its enclosing scope. Variables are file-local and scope-local — a `$user` in method A must not match `$user` in method B.
(
&self,
uri: &str,
content: &str,
var_name: &str,
cursor_offset: u32,
include_declaration: bool,
)
| 253 | /// Variables are file-local and scope-local — a `$user` in method A |
| 254 | /// must not match `$user` in method B. |
| 255 | fn find_variable_references( |
| 256 | &self, |
| 257 | uri: &str, |
| 258 | content: &str, |
| 259 | var_name: &str, |
| 260 | cursor_offset: u32, |
| 261 | include_declaration: bool, |
| 262 | ) -> Vec<Location> { |
| 263 | let mut locations = Vec::new(); |
| 264 | |
| 265 | let maps = self.symbol_maps.read(); |
| 266 | let symbol_map = match maps.get(uri) { |
| 267 | Some(m) => m, |
| 268 | None => return locations, |
| 269 | }; |
| 270 | |
| 271 | // Determine the effective scope for this variable. |
| 272 | // |
| 273 | // `find_variable_scope` handles the tricky cases where the |
| 274 | // cursor is on a parameter (physically before the `{`) or on |
| 275 | // a docblock `@param $var` mention, returning the body scope |
| 276 | // those tokens logically belong to. |
| 277 | let scope_start = symbol_map.find_variable_scope(var_name, cursor_offset); |
| 278 | |
| 279 | let parsed_uri = match Url::parse(uri) { |
| 280 | Ok(u) => u, |
| 281 | Err(_) => return locations, |
| 282 | }; |
| 283 | |
| 284 | // Build the set of reachable scopes: the primary scope plus any |
| 285 | // closure/arrow-function scopes that capture this variable. |
| 286 | let reachable_scopes = Self::collect_capture_scopes(symbol_map, var_name, scope_start); |
| 287 | |
| 288 | for span in &symbol_map.spans { |
| 289 | if let SymbolKind::Variable { name } = &span.kind { |
| 290 | if name != var_name { |
| 291 | continue; |
| 292 | } |
| 293 | // Check that this variable is in a reachable scope. |
| 294 | let span_scope = symbol_map.find_variable_scope(name, span.start); |
| 295 | if !reachable_scopes.contains(&span_scope) { |
| 296 | continue; |
| 297 | } |
| 298 | // Optionally skip declaration sites. |
| 299 | if !include_declaration && symbol_map.var_def_kind_at(name, span.start).is_some() { |
| 300 | continue; |
| 301 | } |
| 302 | let start = offset_to_position(content, span.start as usize); |
| 303 | let end = offset_to_position(content, span.end as usize); |
| 304 | locations.push(Location { |
| 305 | uri: parsed_uri.clone(), |
| 306 | range: Range { start, end }, |
| 307 | }); |
| 308 | } |
| 309 | } |
| 310 | |
| 311 | // Also include var_def sites if include_declaration is set, |
| 312 | // since some definition tokens (parameters, foreach bindings) |
no test coverage detected