Build completion items for array shape keys. Given an `ArrayKeyContext`, resolves the variable's type annotation and, if it is an array shape, returns completion items for each key. Uses `text_edit` with a range that covers any auto-inserted trailing characters (closing quote + `]`) so that accepting a completion does not produce duplicate brackets.
(
&self,
ctx: &ArrayKeyContext,
content: &str,
position: Position,
file_ctx: &FileContext,
)
| 258 | /// characters (closing quote + `]`) so that accepting a completion does |
| 259 | /// not produce duplicate brackets. |
| 260 | pub(crate) fn build_array_key_completions( |
| 261 | &self, |
| 262 | ctx: &ArrayKeyContext, |
| 263 | content: &str, |
| 264 | position: Position, |
| 265 | file_ctx: &FileContext, |
| 266 | ) -> Vec<CompletionItem> { |
| 267 | // ── $_SERVER superglobal — hardcoded keys ──────────────────── |
| 268 | if ctx.var_name == "$_SERVER" && ctx.prefix_keys.is_empty() { |
| 269 | return self.build_server_key_completions(ctx, content, position); |
| 270 | } |
| 271 | |
| 272 | let cursor_offset = position_to_offset(content, position); |
| 273 | |
| 274 | // Try to find the raw type annotation for this variable. |
| 275 | // We also track which set of classes was used for resolution so |
| 276 | // that type alias expansion can consult the same set (important |
| 277 | // when the original parse fails and patched classes are used). |
| 278 | let raw_type = self.resolve_variable_raw_type( |
| 279 | &ctx.var_name, |
| 280 | content, |
| 281 | cursor_offset as usize, |
| 282 | file_ctx, |
| 283 | ); |
| 284 | |
| 285 | // If initial resolution failed, the content likely has a syntax |
| 286 | // error (e.g. unclosed `$var['`) that prevented the parser from |
| 287 | // recovering the class structure. Patch the cursor line to close |
| 288 | // the array access, re-parse, and retry. |
| 289 | let patched_classes_storage; |
| 290 | let (raw_type, effective_classes) = match raw_type { |
| 291 | Some(t) => (t, file_ctx.classes.as_slice()), |
| 292 | None => { |
| 293 | let patched = patch_array_access_at_cursor(content, position); |
| 294 | if patched == content { |
| 295 | return vec![]; |
| 296 | } |
| 297 | patched_classes_storage = self |
| 298 | .parse_php(&patched) |
| 299 | .into_iter() |
| 300 | .map(Arc::new) |
| 301 | .collect::<Vec<_>>(); |
| 302 | let patched_offset = position_to_offset(&patched, position); |
| 303 | let patched_ctx = FileContext { |
| 304 | classes: patched_classes_storage.clone(), |
| 305 | use_map: file_ctx.use_map.clone(), |
| 306 | namespace: file_ctx.namespace.clone(), |
| 307 | resolved_names: file_ctx.resolved_names.clone(), |
| 308 | }; |
| 309 | match self.resolve_variable_raw_type( |
| 310 | &ctx.var_name, |
| 311 | &patched, |
| 312 | patched_offset as usize, |
| 313 | &patched_ctx, |
| 314 | ) { |
| 315 | Some(t) => (t, patched_classes_storage.as_slice()), |
| 316 | None => return vec![], |
| 317 | } |
no test coverage detected