The **full-scan**: a single-pass byte-level scanner that extracts fully-qualified class, function, and constant names from PHP source bytes. This is the extended version of [`find_classes`] (the PSR-4 scanner) that also recognises `function` declarations, `define()` calls, and top-level `const` statements. It is used for both non-Composer projects (full workspace scan) and Composer autoload file
(content: &[u8])
| 828 | /// projects (full workspace scan) and Composer autoload files |
| 829 | /// (`autoload_files.php` and their `require_once` chains). |
| 830 | pub fn find_symbols(content: &[u8]) -> ScanResult { |
| 831 | // Quick rejection — if the file has none of the relevant keywords |
| 832 | // we can bail immediately. |
| 833 | if !has_any_keyword(content) { |
| 834 | return ScanResult::default(); |
| 835 | } |
| 836 | |
| 837 | let mut result = ScanResult::default(); |
| 838 | let mut namespace = String::new(); |
| 839 | let len = content.len(); |
| 840 | let mut i = 0; |
| 841 | |
| 842 | // Brace depth tracking for top-level `const` detection. |
| 843 | // Depth 0 = top-level, depth 1 = inside a class/namespace block. |
| 844 | let mut brace_depth: u32 = 0; |
| 845 | // Whether we are inside a braced namespace block. |
| 846 | let mut in_braced_namespace = false; |
| 847 | // The brace depth at which the current namespace was opened. |
| 848 | // `const` declarations at this depth (or depth 0 outside braced |
| 849 | // namespaces) are top-level. |
| 850 | let mut namespace_brace_depth: u32 = 0; |
| 851 | |
| 852 | // State flags |
| 853 | let mut in_line_comment = false; |
| 854 | let mut in_block_comment = false; |
| 855 | let mut in_single_string = false; |
| 856 | let mut in_double_string = false; |
| 857 | let mut in_heredoc = false; |
| 858 | let mut heredoc_id: &[u8] = &[]; |
| 859 | |
| 860 | while i < len { |
| 861 | // ── Skip: line comment (memchr to newline) ────────────────── |
| 862 | if in_line_comment { |
| 863 | if let Some(pos) = memchr(b'\n', &content[i..]) { |
| 864 | i += pos + 1; |
| 865 | } else { |
| 866 | break; // rest of file is a comment |
| 867 | } |
| 868 | in_line_comment = false; |
| 869 | continue; |
| 870 | } |
| 871 | |
| 872 | // ── Skip: block comment (memchr to '*') ───────────────────── |
| 873 | if in_block_comment { |
| 874 | if let Some(pos) = memchr(b'*', &content[i..]) { |
| 875 | i += pos; |
| 876 | if i + 1 < len && content[i + 1] == b'/' { |
| 877 | in_block_comment = false; |
| 878 | i += 2; |
| 879 | } else { |
| 880 | i += 1; |
| 881 | } |
| 882 | } else { |
| 883 | break; // unclosed block comment |
| 884 | } |
| 885 | continue; |
| 886 | } |
| 887 |