Classify the PHP symbol from the first meaningful tokens.
(text: &str)
| 429 | |
| 430 | /// Classify the PHP symbol from the first meaningful tokens. |
| 431 | fn classify_from_tokens(text: &str) -> DocblockContext { |
| 432 | // Track whether a blank line appears before the first code line. |
| 433 | // Inline `@var` annotations must be on the line immediately before |
| 434 | // the variable assignment — a blank line in between means the |
| 435 | // docblock is not attached to the assignment. |
| 436 | // |
| 437 | // The text starts right after `*/`, so the very first line is the |
| 438 | // remainder of the closing `*/` line (typically empty or whitespace). |
| 439 | // That first line is NOT a real blank gap — only subsequent empty |
| 440 | // lines count. |
| 441 | let mut saw_blank_line = false; |
| 442 | let mut first_code_line: Option<&str> = None; |
| 443 | let mut tokens = Vec::new(); |
| 444 | let mut skipped_first_line = false; |
| 445 | for line in text.lines() { |
| 446 | let trimmed = line.trim(); |
| 447 | if trimmed.is_empty() { |
| 448 | if !skipped_first_line { |
| 449 | // The tail of the `*/` line — always skip it. |
| 450 | skipped_first_line = true; |
| 451 | } else if tokens.is_empty() { |
| 452 | // A real blank line before any code. |
| 453 | saw_blank_line = true; |
| 454 | } |
| 455 | continue; |
| 456 | } |
| 457 | skipped_first_line = true; |
| 458 | if trimmed.starts_with('*') || trimmed.starts_with("/**") { |
| 459 | continue; |
| 460 | } |
| 461 | if first_code_line.is_none() { |
| 462 | first_code_line = Some(trimmed); |
| 463 | } |
| 464 | for word in trimmed.split_whitespace() { |
| 465 | // Store original casing so that uppercase-initial class names |
| 466 | // (e.g. `Collection`) are recognised as type hints later. |
| 467 | tokens.push(word.to_string()); |
| 468 | if tokens.len() >= 6 { |
| 469 | break; |
| 470 | } |
| 471 | } |
| 472 | if tokens.len() >= 6 { |
| 473 | break; |
| 474 | } |
| 475 | } |
| 476 | |
| 477 | if tokens.is_empty() { |
| 478 | return DocblockContext::Unknown; |
| 479 | } |
| 480 | |
| 481 | let mut saw_modifier = false; |
| 482 | for token in &tokens { |
| 483 | let t = token.as_str(); |
| 484 | let lower = t.to_ascii_lowercase(); |
| 485 | match lower.as_str() { |
| 486 | "function" => return DocblockContext::FunctionOrMethod, |
| 487 | "class" | "interface" | "trait" | "enum" => return DocblockContext::ClassLike, |
| 488 | "const" => return DocblockContext::Constant, |
no test coverage detected