Extract Eloquent cast definitions from a class's members. Scans the class members for: 1. A `$casts` property with an array initializer (`protected $casts = [...]`) 2. A `casts()` method whose body contains a `return [...]` statement Returns a list of `(column_name, cast_type)` pairs extracted from the array literal text. Both sources are merged: entries from the `casts()` method take priority
(
members: impl Iterator<Item = &'a ClassLikeMember<'a>>,
content: &str,
)
| 369 | /// the same column appears in both. This matches Laravel's runtime |
| 370 | /// behaviour where `Model::casts()` overrides `$casts`. |
| 371 | fn extract_casts_definitions<'a>( |
| 372 | members: impl Iterator<Item = &'a ClassLikeMember<'a>>, |
| 373 | content: &str, |
| 374 | ) -> Vec<(String, String)> { |
| 375 | let mut property_text: Option<String> = None; |
| 376 | let mut method_text: Option<String> = None; |
| 377 | |
| 378 | for member in members { |
| 379 | match member { |
| 380 | ClassLikeMember::Property(Property::Plain(plain)) => { |
| 381 | for item in plain.items.iter() { |
| 382 | let var_name = item.variable().name.to_string(); |
| 383 | let stripped = var_name.strip_prefix('$').unwrap_or(&var_name); |
| 384 | if stripped != "casts" { |
| 385 | continue; |
| 386 | } |
| 387 | if let PropertyItem::Concrete(concrete) = item { |
| 388 | let span = concrete.value.span(); |
| 389 | let start = span.start.offset as usize; |
| 390 | let end = span.end.offset as usize; |
| 391 | if let Some(text) = content.get(start..end) { |
| 392 | property_text = Some(text.to_string()); |
| 393 | } |
| 394 | } |
| 395 | } |
| 396 | } |
| 397 | ClassLikeMember::Method(method) if method.name.value == "casts" => { |
| 398 | if let MethodBody::Concrete(block) = &method.body { |
| 399 | let start = block.left_brace.start.offset as usize; |
| 400 | let end = block.right_brace.end.offset as usize; |
| 401 | if let Some(text) = content.get(start..end) { |
| 402 | method_text = Some(text.to_string()); |
| 403 | } |
| 404 | } |
| 405 | } |
| 406 | _ => {} |
| 407 | } |
| 408 | } |
| 409 | |
| 410 | // Start with $casts property entries as the base. |
| 411 | let mut merged: Vec<(String, String)> = Vec::new(); |
| 412 | |
| 413 | if let Some(ref text) = property_text { |
| 414 | merged = parse_casts_array(text); |
| 415 | } |
| 416 | |
| 417 | // Merge casts() method entries on top — method entries override |
| 418 | // property entries for the same column, matching Laravel's runtime |
| 419 | // behaviour. |
| 420 | if let Some(ref text) = method_text |
| 421 | && let Some(arr_start) = text.find("return") |
| 422 | { |
| 423 | let after_return = &text[arr_start + 6..]; |
| 424 | if let Some(bracket_pos) = after_return.find('[') { |
| 425 | let array_text = &after_return[bracket_pos..]; |
| 426 | let method_defs = parse_casts_array(array_text); |
| 427 | for (key, value) in method_defs { |
| 428 | if let Some(existing) = merged.iter_mut().find(|(k, _)| *k == key) { |
no test coverage detected