Skip to main content

weavatrix_parse/
braced.rs

1//! Structural extraction for the brace-scoped languages.
2//!
3//! Rust, Go, Java, C#, C, C++ and Solidity differ in which keyword introduces
4//! a declaration and how a module is named, and agree on everything else:
5//! braces open bodies, a name followed by a parameter list is callable, and a
6//! call is an identifier followed by `(`. Those differences are tables, so one
7//! walk serves all seven instead of seven near-identical scanners - and adding
8//! the next such language costs a table, not a scanner.
9
10use crate::facts::{
11    Declaration, DeclarationKind, Facts, Import, ImportBinding, Reference, ReferenceKind, Span,
12};
13use crate::syntax::Language;
14use crate::token::{Mode, Token, TokenKind, Tokenizer};
15
16/// Extracts structural facts from one brace-scoped source file.
17#[must_use]
18pub fn extract(source: &str, language: Language) -> Facts {
19    let tokens = Tokenizer::new(source, language)
20        .mode(Mode::Lite)
21        .collect::<Vec<_>>();
22    let mut state = Extractor {
23        source,
24        tokens: &tokens,
25        language,
26        rules: Rules::of(language),
27        facts: Facts::default(),
28        scopes: Vec::new(),
29        depth: 0,
30    };
31    state.run();
32    state.facts
33}
34
35/// Keywords one language uses, as data.
36struct Rules {
37    /// Keyword to the kind it declares.
38    declarations: &'static [(&'static str, DeclarationKind)],
39    /// Keywords that introduce a module dependency.
40    imports: &'static [&'static str],
41    /// Modifiers to step over before the declaring keyword.
42    modifiers: &'static [&'static str],
43    /// Whether a bare `name(` at type-body depth declares a method.
44    braced_members: bool,
45    /// Whether `const (...)` and `var (...)` contain one declaration spec per
46    /// top-level line. This is Go syntax, not a generic braced-language rule.
47    grouped_declarations: bool,
48    /// Whether a function is declared by a return type rather than a keyword,
49    /// as C and C++ do: `int add(int a, int b) { }`.
50    typed_functions: bool,
51    /// Whether a declaration is public by keyword rather than by convention.
52    exported_keyword: Option<&'static str>,
53    /// Keywords that open a named scope without declaring anything: Rust's
54    /// `impl Type` and Swift's `extension Type` say what the members belong
55    /// to, and declare no new name.
56    scope_keywords: &'static [&'static str],
57}
58
59impl Rules {
60    // One arm per language, each a table of keywords. Splitting it to satisfy
61    // a line count would scatter the tables and make the languages harder to
62    // compare against each other, which is the point of writing them as data.
63    #[allow(clippy::too_many_lines)]
64    const fn of(language: Language) -> Self {
65        match language {
66            Language::Rust => Self {
67                declarations: &[
68                    ("fn", DeclarationKind::Function),
69                    ("struct", DeclarationKind::Struct),
70                    ("enum", DeclarationKind::Enum),
71                    ("trait", DeclarationKind::Trait),
72                    ("type", DeclarationKind::TypeAlias),
73                    ("const", DeclarationKind::Constant),
74                    ("static", DeclarationKind::Constant),
75                    ("mod", DeclarationKind::Module),
76                ],
77                imports: &["use", "mod"],
78                modifiers: &["pub", "async", "unsafe", "extern", "default"],
79                braced_members: false,
80                grouped_declarations: false,
81                typed_functions: false,
82                exported_keyword: Some("pub"),
83                scope_keywords: &["impl"],
84            },
85            Language::Swift => Self {
86                declarations: &[
87                    ("func", DeclarationKind::Function),
88                    ("class", DeclarationKind::Class),
89                    ("struct", DeclarationKind::Struct),
90                    ("actor", DeclarationKind::Class),
91                    ("enum", DeclarationKind::Enum),
92                    ("protocol", DeclarationKind::Interface),
93                    ("typealias", DeclarationKind::TypeAlias),
94                    ("associatedtype", DeclarationKind::TypeAlias),
95                    ("let", DeclarationKind::Constant),
96                    ("var", DeclarationKind::Variable),
97                    ("init", DeclarationKind::Method),
98                    ("subscript", DeclarationKind::Method),
99                ],
100                imports: &["import"],
101                modifiers: &[
102                    "public",
103                    "private",
104                    "internal",
105                    "fileprivate",
106                    "open",
107                    "static",
108                    "final",
109                    "override",
110                    "mutating",
111                    "nonmutating",
112                    "lazy",
113                    "weak",
114                    "unowned",
115                    "required",
116                    "convenience",
117                    "indirect",
118                    "dynamic",
119                    "optional",
120                    "async",
121                    "throws",
122                ],
123                braced_members: false,
124                grouped_declarations: false,
125                typed_functions: false,
126                // `open` is wider than `public`, but both leave the module,
127                // and `exported` records only whether it leaves.
128                exported_keyword: Some("public"),
129                scope_keywords: &["extension"],
130            },
131            Language::Go => Self {
132                declarations: &[
133                    ("func", DeclarationKind::Function),
134                    ("type", DeclarationKind::Struct),
135                    ("const", DeclarationKind::Constant),
136                    ("var", DeclarationKind::Variable),
137                ],
138                imports: &["import"],
139                modifiers: &[],
140                braced_members: false,
141                grouped_declarations: true,
142                typed_functions: false,
143                exported_keyword: None,
144                scope_keywords: &[],
145            },
146            Language::Java | Language::CSharp => Self {
147                declarations: &[
148                    ("class", DeclarationKind::Class),
149                    ("interface", DeclarationKind::Interface),
150                    ("enum", DeclarationKind::Enum),
151                    ("record", DeclarationKind::Struct),
152                    ("struct", DeclarationKind::Struct),
153                ],
154                imports: &["import", "using"],
155                modifiers: &[
156                    "public",
157                    "private",
158                    "protected",
159                    "static",
160                    "final",
161                    "abstract",
162                    "sealed",
163                    "internal",
164                    "override",
165                    "async",
166                    "virtual",
167                    "readonly",
168                ],
169                braced_members: true,
170                grouped_declarations: false,
171                typed_functions: false,
172                exported_keyword: Some("public"),
173                scope_keywords: &[],
174            },
175            Language::Solidity => Self {
176                declarations: &[
177                    ("contract", DeclarationKind::Class),
178                    ("library", DeclarationKind::Class),
179                    ("interface", DeclarationKind::Interface),
180                    ("struct", DeclarationKind::Struct),
181                    ("enum", DeclarationKind::Enum),
182                    ("function", DeclarationKind::Function),
183                    ("constructor", DeclarationKind::Method),
184                    ("modifier", DeclarationKind::Function),
185                    ("event", DeclarationKind::Field),
186                    ("error", DeclarationKind::Struct),
187                ],
188                imports: &["import"],
189                modifiers: &[
190                    "abstract", "virtual", "override", "public", "private", "internal", "external",
191                    "pure", "view", "payable",
192                ],
193                braced_members: false,
194                grouped_declarations: false,
195                typed_functions: false,
196                // Anything not marked internal or private is reachable from
197                // another contract, which is what export means here.
198                exported_keyword: Some("public"),
199                scope_keywords: &[],
200            },
201            _ => Self {
202                declarations: &[
203                    ("struct", DeclarationKind::Struct),
204                    ("class", DeclarationKind::Class),
205                    ("enum", DeclarationKind::Enum),
206                    ("namespace", DeclarationKind::Module),
207                ],
208                imports: &["#include", "include"],
209                modifiers: &["static", "inline", "extern", "const", "virtual"],
210                braced_members: true,
211                grouped_declarations: false,
212                typed_functions: true,
213                exported_keyword: None,
214                scope_keywords: &[],
215            },
216        }
217    }
218}
219
220struct Scope {
221    name: String,
222    depth: Option<i32>,
223    type_body: bool,
224    test_only: bool,
225}
226
227struct Extractor<'source, 'tokens> {
228    source: &'source str,
229    tokens: &'tokens [Token],
230    language: Language,
231    rules: Rules,
232    facts: Facts,
233    scopes: Vec<Scope>,
234    depth: i32,
235}
236
237impl Extractor<'_, '_> {
238    fn run(&mut self) {
239        let mut index = 0;
240        while index < self.tokens.len() {
241            self.close_scopes();
242            index = self.step(index);
243        }
244    }
245
246    fn text(&self, index: usize) -> &str {
247        self.tokens
248            .get(index)
249            .map_or("", |token| token.text(self.source))
250    }
251
252    fn kind(&self, index: usize) -> Option<TokenKind> {
253        self.tokens.get(index).map(|token| token.kind)
254    }
255
256    fn punct(&self, index: usize, mark: &str) -> bool {
257        self.kind(index) == Some(TokenKind::Punctuation) && self.text(index) == mark
258    }
259
260    fn span(&self, start: usize, end: usize) -> Span {
261        let last_index = self.tokens.len().saturating_sub(1);
262        let first = &self.tokens[start.min(last_index)];
263        let last = &self.tokens[end.min(last_index)];
264        Span {
265            start: first.start,
266            end: last.end,
267            line: first.line,
268            column: first.column,
269            end_line: last.line,
270            end_column: last.column,
271        }
272    }
273
274    fn owner(&self) -> Option<String> {
275        self.scopes.last().map(|scope| scope.name.clone())
276    }
277
278    fn test_only_at(&self, index: usize) -> bool {
279        if self.language != Language::Rust {
280            return false;
281        }
282        self.scopes.last().is_some_and(|scope| scope.test_only)
283            || self.rust_test_attribute_before(index)
284    }
285
286    fn record_test_only_declaration(&mut self, test_only: bool, span: Span) {
287        if test_only {
288            self.facts.test_only_declarations.push(span);
289        }
290    }
291
292    /// Reads only the contiguous Rust attributes immediately before an item.
293    ///
294    /// The lossless tokenizer has already removed comments and strings from
295    /// syntax consideration, so a quoted `#[cfg(test)]` cannot classify code.
296    fn rust_test_attribute_before(&self, index: usize) -> bool {
297        let mut cursor = index;
298        while cursor > 0 && self.punct(cursor - 1, "]") {
299            let end = cursor - 1;
300            let mut start = end;
301            let mut depth = 1_i32;
302            while start > 0 {
303                start -= 1;
304                if self.punct(start, "]") {
305                    depth += 1;
306                } else if self.punct(start, "[") {
307                    depth -= 1;
308                    if depth == 0 {
309                        break;
310                    }
311                }
312            }
313            if depth != 0 || start == 0 || !self.punct(start - 1, "#") {
314                break;
315            }
316            if self.rust_attribute_is_test(start + 1, end) {
317                return true;
318            }
319            cursor = start - 1;
320        }
321        false
322    }
323
324    fn rust_attribute_is_test(&self, start: usize, end: usize) -> bool {
325        let Some(first) =
326            (start..end).find(|&index| self.kind(index) == Some(TokenKind::Identifier))
327        else {
328            return false;
329        };
330        if self.text(first) == "cfg" {
331            let Some(open) = (first + 1..end).find(|&index| self.punct(index, "(")) else {
332                return false;
333            };
334            return self.cfg_has_positive_test(open + 1, end, false);
335        }
336        let path_end = (first..end)
337            .find(|&index| self.punct(index, "("))
338            .unwrap_or(end);
339        (first..path_end)
340            .rfind(|&index| self.kind(index) == Some(TokenKind::Identifier))
341            .is_some_and(|index| {
342                matches!(
343                    self.text(index),
344                    "test" | "rstest" | "proptest" | "wasm_bindgen_test" | "test_case"
345                )
346            })
347    }
348
349    fn cfg_has_positive_test(&self, start: usize, end: usize, negated: bool) -> bool {
350        let mut cursor = start;
351        while cursor < end {
352            if self.kind(cursor) == Some(TokenKind::Identifier) {
353                if self.text(cursor) == "not" && self.punct(cursor + 1, "(") {
354                    let close = self.matching_paren(cursor + 1, end);
355                    if self.cfg_has_positive_test(cursor + 2, close, !negated) {
356                        return true;
357                    }
358                    cursor = close.saturating_add(1);
359                    continue;
360                }
361                if self.text(cursor) == "test" && !negated {
362                    return true;
363                }
364            }
365            cursor += 1;
366        }
367        false
368    }
369
370    fn matching_paren(&self, open: usize, end: usize) -> usize {
371        let mut depth = 0_i32;
372        for cursor in open..end {
373            if self.punct(cursor, "(") {
374                depth += 1;
375            } else if self.punct(cursor, ")") {
376                depth -= 1;
377                if depth == 0 {
378                    return cursor;
379                }
380            }
381        }
382        end
383    }
384
385    fn close_scopes(&mut self) {
386        while self
387            .scopes
388            .last()
389            .is_some_and(|scope| scope.depth.is_some_and(|depth| self.depth < depth))
390        {
391            self.scopes.pop();
392        }
393    }
394
395    /// Discards a scope still waiting for a body when a second declaration
396    /// arrives, because only one can be waiting at a time.
397    ///
398    /// A semicolon already does this for languages that write one. Swift and
399    /// Go end a statement at the newline, so without this a `let name: String`
400    /// stayed open and adopted every function declared after it.
401    fn drop_waiting(&mut self) {
402        if self
403            .scopes
404            .last()
405            .is_some_and(|scope| scope.depth.is_none())
406        {
407            self.scopes.pop();
408        }
409    }
410
411    fn open_body(&mut self) {
412        let depth = self.depth;
413        if let Some(scope) = self.scopes.last_mut()
414            && scope.depth.is_none()
415        {
416            scope.depth = Some(depth);
417        }
418    }
419
420    fn step(&mut self, index: usize) -> usize {
421        if self.punct(index, "{") {
422            self.depth += 1;
423            self.open_body();
424            return index + 1;
425        }
426        if self.punct(index, "}") {
427            self.depth -= 1;
428            return index + 1;
429        }
430        if self.punct(index, ";") {
431            // A declaration whose statement ended before any brace has no
432            // body, so it must not stay open and adopt what follows it -
433            // which is what a Solidity `event` did to the next function.
434            if self
435                .scopes
436                .last()
437                .is_some_and(|scope| scope.depth.is_none())
438            {
439                self.scopes.pop();
440            }
441            return index + 1;
442        }
443        if self.kind(index) != Some(TokenKind::Identifier) {
444            return index + 1;
445        }
446        if let Some(next) = self.import(index) {
447            return next;
448        }
449        if let Some(next) = self.declaration(index) {
450            return next;
451        }
452        if let Some(next) = self.call(index) {
453            return next;
454        }
455        index + 1
456    }
457
458    /// The module forms these languages write: `use a::b;`, `mod x;`,
459    /// `import "path"`, grouped Go imports, `import a.b.C;`, `using X;`.
460    // One pass keeps the shared statement boundaries beside the language
461    // spellings; those fail-open limits are what prevent run-on imports.
462    #[allow(clippy::too_many_lines)]
463    fn import(&mut self, start: usize) -> Option<usize> {
464        // `pub mod x;` is still a module dependency, so modifiers are stepped
465        // over here exactly as a declaration would step over them.
466        let mut index = start;
467        // `pub use x::y;` forwards another module's surface to importers of
468        // this one, exactly as `export ... from` does in JavaScript, so an
469        // importer reaches through it transitively.
470        let forwarding = self.rules.exported_keyword == Some(self.text(start));
471        while self.rules.modifiers.contains(&self.text(index)) {
472            index += 1;
473            // `pub(crate) use x;` carries a parenthesised visibility scope.
474            if self.punct(index, "(") {
475                while index < self.tokens.len() && !self.punct(index, ")") {
476                    index += 1;
477                }
478                index += 1;
479            }
480        }
481        let word = self.text(index);
482        if !self.rules.imports.contains(&word) {
483            return None;
484        }
485        let mut bindings = Vec::new();
486        // A parenthesised block lists several paths, as Go writes them.
487        if self.punct(index + 1, "(") {
488            let mut cursor = index + 2;
489            let limit = (index + 512).min(self.tokens.len());
490            while cursor < limit && !self.punct(cursor, ")") {
491                if self.kind(cursor) == Some(TokenKind::String) {
492                    let specifier = self.text(cursor).trim_matches(['"', '`']).to_owned();
493                    let bindings = self.package_import_bindings(cursor, &specifier);
494                    let names = bindings
495                        .iter()
496                        .map(|binding| binding.local.clone())
497                        .collect();
498                    self.facts.imports.push(Import {
499                        specifier,
500                        span: self.span(cursor, cursor),
501                        type_only: false,
502                        reexport: false,
503                        names,
504                        bindings,
505                    });
506                }
507                cursor += 1;
508            }
509            return Some(cursor + 1);
510        }
511        // A `mod x { ... }` with a body defines the module here; only a
512        // declaration without a body pulls in another file.
513        if word == "mod" {
514            let name = self.text(index + 1);
515            if name.is_empty() || !self.punct(index + 2, ";") {
516                return None;
517            }
518            self.facts.imports.push(Import {
519                specifier: format!("self::{name}"),
520                span: self.span(index, index + 1),
521                type_only: false,
522                reexport: false,
523                names: Vec::new(),
524                bindings: Vec::new(),
525            });
526            return Some(index + 2);
527        }
528        // A quoted path is the whole specifier; otherwise the specifier runs
529        // to the statement end.
530        let line = self.tokens[index].line;
531        let mut cursor = index + 1;
532        let mut specifier = String::new();
533        let limit = (index + 128).min(self.tokens.len());
534        while cursor < limit {
535            if self.kind(cursor) == Some(TokenKind::String) {
536                // A quoted path is the whole specifier, so anything read
537                // before it was a list of imported names, not a path.
538                specifier.clear();
539                specifier.push_str(self.text(cursor).trim_matches(['"', '`', '\'']));
540                if word == "import"
541                    && bindings.is_empty()
542                    && self.text(cursor.wrapping_sub(1)) != "from"
543                {
544                    bindings = self.package_import_bindings(cursor, &specifier);
545                }
546                cursor += 1;
547                break;
548            }
549            if self.punct(cursor, ";") {
550                break;
551            }
552            if self.punct(cursor, "{") {
553                // A trailing group narrows a path already read, as in
554                // `use a::{b, c}`. A leading one lists names being imported
555                // from a path still to come, as in `import {A} from "./x"`.
556                let mut close = cursor + 1;
557                while close < limit && !self.punct(close, "}") {
558                    close += 1;
559                }
560                bindings.extend(self.named_import_bindings(cursor + 1, close));
561                if !specifier.is_empty() {
562                    cursor = close.saturating_add(1);
563                    break;
564                }
565                cursor = close.saturating_add(1);
566                continue;
567            }
568            if word == "use"
569                && self.text(cursor) == "as"
570                && self.kind(cursor + 1) == Some(TokenKind::Identifier)
571            {
572                let imported = specifier
573                    .trim_end_matches(':')
574                    .rsplit("::")
575                    .next()
576                    .unwrap_or(specifier.as_str())
577                    .to_owned();
578                bindings.push(ImportBinding {
579                    imported,
580                    local: self.text(cursor + 1).to_owned(),
581                });
582                cursor += 2;
583                continue;
584            }
585            // A specifier ends with its line. Reading on would swallow what
586            // follows, which is how `#include <stdio.h>` consumed the function
587            // defined beneath it. While nothing has been read yet the scan may
588            // still cross a line, because `import {A} from` puts its path on
589            // the next one.
590            if self.tokens[cursor].line != line && !specifier.is_empty() {
591                break;
592            }
593            if matches!(
594                self.kind(cursor),
595                Some(TokenKind::Identifier | TokenKind::Punctuation)
596            ) {
597                specifier.push_str(self.text(cursor));
598            }
599            cursor += 1;
600        }
601        // `use a::b::{c, d}` stops at the brace, leaving a separator behind.
602        let specifier = specifier.trim_end_matches([':', '.']).to_owned();
603        if specifier.is_empty() {
604            return None;
605        }
606        if word == "use" && bindings.is_empty() {
607            let imported = specifier
608                .rsplit("::")
609                .next()
610                .unwrap_or(specifier.as_str())
611                .to_owned();
612            if imported != "*" {
613                bindings.push(ImportBinding {
614                    local: imported.clone(),
615                    imported,
616                });
617            }
618        }
619        let names = bindings
620            .iter()
621            .map(|binding| binding.local.clone())
622            .collect();
623        self.facts.imports.push(Import {
624            specifier,
625            span: self.span(index, cursor.saturating_sub(1)),
626            type_only: false,
627            reexport: forwarding,
628            names,
629            bindings,
630        });
631        Some(cursor)
632    }
633
634    fn named_import_bindings(&self, start: usize, end: usize) -> Vec<ImportBinding> {
635        let mut bindings = Vec::new();
636        let mut cursor = start;
637        while cursor < end {
638            if self.kind(cursor) != Some(TokenKind::Identifier)
639                || matches!(self.text(cursor), "as" | "type")
640                || self.text(cursor.wrapping_sub(1)) == "as"
641            {
642                cursor += 1;
643                continue;
644            }
645            let imported = self.text(cursor).to_owned();
646            let local = if self.text(cursor + 1) == "as"
647                && self.kind(cursor + 2) == Some(TokenKind::Identifier)
648            {
649                self.text(cursor + 2).to_owned()
650            } else {
651                imported.clone()
652            };
653            bindings.push(ImportBinding { imported, local });
654            cursor += 1;
655        }
656        bindings
657    }
658
659    fn package_import_bindings(&self, path: usize, specifier: &str) -> Vec<ImportBinding> {
660        let imported = specifier
661            .trim_end_matches('/')
662            .rsplit('/')
663            .next()
664            .unwrap_or(specifier)
665            .to_owned();
666        let previous = path.wrapping_sub(1);
667        let same_line = self
668            .tokens
669            .get(previous)
670            .is_some_and(|token| token.line == self.tokens[path].line);
671        let local = if same_line
672            && self.kind(previous) == Some(TokenKind::Identifier)
673            && !matches!(self.text(previous), "import" | "from")
674        {
675            self.text(previous).to_owned()
676        } else if same_line && self.punct(previous, ".") {
677            "*".to_owned()
678        } else {
679            imported.clone()
680        };
681        if local == "_" {
682            Vec::new()
683        } else {
684            vec![ImportBinding { imported, local }]
685        }
686    }
687
688    fn declaration(&mut self, index: usize) -> Option<usize> {
689        let mut cursor = index;
690        let mut exported = false;
691        loop {
692            let word = self.text(cursor);
693            if self.rules.exported_keyword == Some(word) {
694                exported = true;
695            }
696            if self.rules.modifiers.contains(&word) {
697                cursor += 1;
698                // `pub(crate)` and similar carry a parenthesised scope.
699                if self.punct(cursor, "(") {
700                    while cursor < self.tokens.len() && !self.punct(cursor, ")") {
701                        cursor += 1;
702                    }
703                    cursor += 1;
704                }
705                continue;
706            }
707            break;
708        }
709        if self.rules.scope_keywords.contains(&self.text(cursor))
710            && let Some(next) = self.open_named_scope(cursor)
711        {
712            return Some(next);
713        }
714        let keyword = self.text(cursor);
715        if keyword == "const" && self.punct(cursor.wrapping_sub(1), "*") {
716            // Rust raw-pointer pointees (`*const T`) are types, not constant
717            // declarations. The declaration walk sees every identifier, so
718            // this boundary must be explicit.
719            return None;
720        }
721        let Some((_, kind)) = self
722            .rules
723            .declarations
724            .iter()
725            .find(|(word, _)| *word == keyword)
726        else {
727            return self
728                .typed_function(cursor, exported)
729                .or_else(|| self.braced_member(cursor, exported));
730        };
731        // Go groups declarations: `const ( A = 1\n B = 2 )` declares both, and
732        // the keyword is followed by a parenthesis rather than by a name.
733        if self.rules.grouped_declarations && self.punct(cursor + 1, "(") {
734            return Some(self.grouped_declarations(index, cursor + 2, *kind));
735        }
736        let name_index = cursor + 1;
737        if self.kind(name_index) != Some(TokenKind::Identifier) {
738            return None;
739        }
740        let name = self.text(name_index).to_owned();
741        // Go marks export by an initial capital rather than a keyword.
742        let exported = exported || name.starts_with(char::is_uppercase);
743        let test_only = self.test_only_at(index);
744        let declaration_span = self.span(index, name_index);
745        self.drop_waiting();
746        self.record_test_only_declaration(test_only, declaration_span);
747        self.facts.declarations.push(Declaration {
748            name: name.clone(),
749            kind: *kind,
750            span: declaration_span,
751            owner: self.owner(),
752            exported,
753        });
754        self.heritage(name_index + 1, &name);
755        self.scopes.push(Scope {
756            name,
757            depth: None,
758            type_body: matches!(
759                kind,
760                DeclarationKind::Class
761                    | DeclarationKind::Struct
762                    | DeclarationKind::Interface
763                    | DeclarationKind::Trait
764                    | DeclarationKind::Enum
765            ),
766            test_only,
767        });
768        Some(name_index + 1)
769    }
770
771    /// What a type declares itself to derive from or satisfy.
772    ///
773    /// `extends` and `implements` are different edges and the graph keeps them
774    /// apart; Go and Solidity write only one relation, so everything after
775    /// their marker inherits.
776    fn heritage(&mut self, start: usize, owner: &str) {
777        let limit = (start + 48).min(self.tokens.len());
778        let mut cursor = start;
779        let mut kind = None;
780        while cursor < limit && !self.punct(cursor, "{") && !self.punct(cursor, ";") {
781            match self.text(cursor) {
782                // Solidity writes `contract Vault is Ownable` for what Java
783                // writes as `extends`.
784                "extends" | "is" => kind = Some(ReferenceKind::Inherits),
785                "implements" => kind = Some(ReferenceKind::Implements),
786                _ => {
787                    if let Some(kind) = kind
788                        && self.kind(cursor) == Some(TokenKind::Identifier)
789                        && !self.punct(cursor.wrapping_sub(1), ".")
790                    {
791                        self.facts.references.push(Reference {
792                            name: self.text(cursor).to_owned(),
793                            kind,
794                            receiver: None,
795                            span: self.span(cursor, cursor),
796                            owner: Some(owner.to_owned()),
797                            string_arguments: Vec::new(),
798                            name_arguments: Vec::new(),
799                        });
800                    }
801                }
802            }
803            cursor += 1;
804        }
805    }
806
807    /// A field written inside a type body: `private String name = value;`.
808    ///
809    /// The name is the last one before the terminator, which is what makes a
810    /// generic type such as `Map<String, Order> index;` name `index` rather
811    /// than one of its type arguments.
812    fn braced_field(&mut self, index: usize, exported: bool) -> Option<usize> {
813        let limit = (index + 32).min(self.tokens.len());
814        let mut cursor = index;
815        let mut name = None;
816        while cursor < limit {
817            if self.punct(cursor, ";") || self.punct(cursor, "=") {
818                break;
819            }
820            // A parenthesis or a brace means this was never a field.
821            if self.punct(cursor, "(") || self.punct(cursor, "{") {
822                return None;
823            }
824            if self.kind(cursor) == Some(TokenKind::Identifier) {
825                name = Some((self.text(cursor).to_owned(), cursor));
826            }
827            cursor += 1;
828        }
829        let (name, at) = name?;
830        // A lone name is a reference, not a declaration: a field needs a type
831        // before it.
832        if at == index {
833            return None;
834        }
835        let declaration_span = self.span(index, at);
836        let test_only = self.test_only_at(index);
837        self.record_test_only_declaration(test_only, declaration_span);
838        self.facts.declarations.push(Declaration {
839            name,
840            kind: DeclarationKind::Field,
841            span: declaration_span,
842            owner: self.owner(),
843            exported,
844        });
845        Some(cursor)
846    }
847
848    /// Every name declared inside a `const (...)` or `var (...)` group.
849    ///
850    /// Each line of the group declares one name, so the first identifier on a
851    /// line is the declaration and the rest is its value.
852    fn grouped_declarations(&mut self, start: usize, open: usize, kind: DeclarationKind) -> usize {
853        let limit = self.tokens.len();
854        let mut cursor = open;
855        let mut line = 0;
856        let mut found = false;
857        let mut closed = false;
858        let mut parentheses = 1_u32;
859        let mut braces = 0_u32;
860        let mut brackets = 0_u32;
861        while cursor < limit && parentheses != 0 {
862            if self.punct(cursor, "(") {
863                parentheses = parentheses.saturating_add(1);
864                cursor += 1;
865                continue;
866            }
867            if self.punct(cursor, ")") {
868                parentheses = parentheses.saturating_sub(1);
869                cursor += 1;
870                if parentheses == 0 {
871                    closed = true;
872                }
873                continue;
874            }
875            if self.punct(cursor, "{") {
876                braces = braces.saturating_add(1);
877                cursor += 1;
878                continue;
879            }
880            if self.punct(cursor, "}") {
881                braces = braces.saturating_sub(1);
882                cursor += 1;
883                continue;
884            }
885            if self.punct(cursor, "[") {
886                brackets = brackets.saturating_add(1);
887                cursor += 1;
888                continue;
889            }
890            if self.punct(cursor, "]") {
891                brackets = brackets.saturating_sub(1);
892                cursor += 1;
893                continue;
894            }
895            if parentheses == 1
896                && braces == 0
897                && brackets == 0
898                && self.kind(cursor) == Some(TokenKind::Identifier)
899                && self.tokens[cursor].line != line
900                && !cursor.checked_sub(1).is_some_and(|previous| {
901                    self.kind(previous) == Some(TokenKind::Punctuation)
902                        && matches!(
903                            self.text(previous),
904                            "=" | ","
905                                | "."
906                                | "+"
907                                | "-"
908                                | "*"
909                                | "/"
910                                | "%"
911                                | "&"
912                                | "|"
913                                | "^"
914                                | "!"
915                                | "<"
916                                | ">"
917                                | ":"
918                        )
919                })
920            {
921                line = self.tokens[cursor].line;
922                let name = self.text(cursor).to_owned();
923                let exported = name.starts_with(char::is_uppercase);
924                let declaration_span = self.span(cursor, cursor);
925                let test_only = self.test_only_at(cursor);
926                self.record_test_only_declaration(test_only, declaration_span);
927                self.facts.declarations.push(Declaration {
928                    name,
929                    kind,
930                    span: declaration_span,
931                    owner: self.owner(),
932                    exported,
933                });
934                found = true;
935            }
936            // Group initializers still contain calls (`flag.String(...)`,
937            // constructors, conversions). The declaration pre-pass must not
938            // make those references disappear merely because it advances over
939            // the complete parenthesised group.
940            if self.kind(cursor) == Some(TokenKind::Identifier)
941                && let Some(next) = self.call(cursor)
942            {
943                cursor = next;
944                continue;
945            }
946            cursor += 1;
947        }
948        if closed && found {
949            cursor
950        } else {
951            // Malformed or pathologically truncated input must not swallow the
952            // remainder of the file.
953            start + 1
954        }
955    }
956
957    /// `impl Type`, `impl Trait for Type` and `extension Type` name what their
958    /// members belong to without declaring anything themselves.
959    ///
960    /// The name is the last one before the brace, which is what makes
961    /// `impl Display for Engine` belong to `Engine` rather than to `Display`.
962    fn open_named_scope(&mut self, keyword: usize) -> Option<usize> {
963        self.drop_waiting();
964        let limit = (keyword + 64).min(self.tokens.len());
965        let mut cursor = keyword + 1;
966        let mut name = None;
967        let mut generic = 0_i32;
968        while cursor < limit && !self.punct(cursor, "{") {
969            if self.punct(cursor, ";") {
970                return None;
971            }
972            // A generic argument is part of the type, not a name of its own.
973            if self.punct(cursor, "<") {
974                generic += 1;
975            } else if self.punct(cursor, ">") {
976                generic -= 1;
977            } else if generic == 0 && self.kind(cursor) == Some(TokenKind::Identifier) {
978                name = Some(self.text(cursor).to_owned());
979            }
980            cursor += 1;
981        }
982        let name = name?;
983        if !self.punct(cursor, "{") {
984            return None;
985        }
986        let test_only = self.test_only_at(keyword);
987        self.scopes.push(Scope {
988            name,
989            depth: None,
990            type_body: true,
991            test_only,
992        });
993        Some(cursor)
994    }
995
996    /// A C or C++ function, which no keyword introduces: what marks it is a
997    /// return type before the name and a body after the parameter list.
998    ///
999    /// Without this, `int add(int a, int b) { }` matched nothing and then fell
1000    /// through to the call path, so every C function definition was recorded
1001    /// as a call to itself - a self-edge in the graph, and no declaration for
1002    /// dead-code analysis to find.
1003    fn typed_function(&mut self, index: usize, exported: bool) -> Option<usize> {
1004        if !self.rules.typed_functions || !self.punct(index + 1, "(") {
1005            return None;
1006        }
1007        let name = self.text(index);
1008        // A control structure is also a name followed by a parenthesis and a
1009        // brace, and `else if (x) {` even has an identifier before it.
1010        if matches!(
1011            name,
1012            "if" | "for" | "while" | "switch" | "return" | "catch" | "sizeof" | "do"
1013        ) {
1014            return None;
1015        }
1016        // What precedes the name decides: a return type, possibly through a
1017        // `Class::` qualifier, means a definition; anything else means a call.
1018        let (owner, type_index) = if self.punct(index - 1, ":")
1019            && self.punct(index.checked_sub(2)?, ":")
1020            && self.kind(index.checked_sub(3)?) == Some(TokenKind::Identifier)
1021        {
1022            (Some(self.text(index - 3).to_owned()), index.checked_sub(4)?)
1023        } else {
1024            (self.owner(), index.checked_sub(1)?)
1025        };
1026        let preceded_by_type = self.kind(type_index) == Some(TokenKind::Identifier)
1027            && !matches!(self.text(type_index), "return" | "else" | "case" | "goto")
1028            || self.punct(type_index, "*")
1029            || self.punct(type_index, "&");
1030        if !preceded_by_type {
1031            return None;
1032        }
1033        // Only a body proves a definition. A prototype ends at a semicolon,
1034        // and so does `return helper(x);` - so prototypes are left alone
1035        // rather than risk reading every call as a declaration.
1036        let mut cursor = index + 2;
1037        let mut depth = 1_i32;
1038        let limit = (index + 512).min(self.tokens.len());
1039        while cursor < limit && depth > 0 {
1040            if self.punct(cursor, "(") {
1041                depth += 1;
1042            } else if self.punct(cursor, ")") {
1043                depth -= 1;
1044            }
1045            cursor += 1;
1046        }
1047        // `const`, `noexcept` and `override` may sit between `)` and the body.
1048        while cursor < limit && self.kind(cursor) == Some(TokenKind::Identifier) {
1049            cursor += 1;
1050        }
1051        if !self.punct(cursor, "{") {
1052            return None;
1053        }
1054        let name = name.to_owned();
1055        let test_only = self.test_only_at(index);
1056        let declaration_span = self.span(index, index);
1057        self.record_test_only_declaration(test_only, declaration_span);
1058        self.facts.declarations.push(Declaration {
1059            name: name.clone(),
1060            kind: if owner.is_some() {
1061                DeclarationKind::Method
1062            } else {
1063                DeclarationKind::Function
1064            },
1065            span: declaration_span,
1066            owner,
1067            // C has no visibility keyword; a static function is file-local and
1068            // everything else is linkable.
1069            exported: exported || !self.is_static(index),
1070        });
1071        self.scopes.push(Scope {
1072            name,
1073            depth: None,
1074            type_body: false,
1075            test_only,
1076        });
1077        Some(index + 1)
1078    }
1079
1080    /// How many tokens the `<...>` group at `index` occupies, or zero when
1081    /// this is a comparison rather than a type list.
1082    ///
1083    /// A closing angle bracket is what tells the two apart: `a < b` never
1084    /// reaches one before the statement ends.
1085    ///
1086    /// This deliberately allocates nothing. Rust writes `Vec<String>` and
1087    /// `Option<T>` everywhere, so this runs on almost every identifier in a
1088    /// file and almost always ends in a type rather than a call - collecting
1089    /// the names here cost a heap allocation per type argument that was then
1090    /// thrown away, and a third of the extraction throughput with it.
1091    fn type_argument_span(&self, index: usize) -> usize {
1092        if !self.punct(index, "<") {
1093            return 0;
1094        }
1095        let limit = (index + 32).min(self.tokens.len());
1096        let mut cursor = index + 1;
1097        let mut depth = 1_i32;
1098        while cursor < limit && depth > 0 {
1099            if self.punct(cursor, "<") {
1100                depth += 1;
1101            } else if self.punct(cursor, ">") {
1102                depth -= 1;
1103            } else if self.punct(cursor, ";") || self.punct(cursor, "{") {
1104                // A statement ended, so the angle bracket was an operator.
1105                return 0;
1106            }
1107            cursor += 1;
1108        }
1109        if depth > 0 { 0 } else { cursor - index }
1110    }
1111
1112    /// The type names inside a group already known to be one.
1113    fn type_argument_names(&self, index: usize, length: usize) -> Vec<String> {
1114        (index..index + length)
1115            .filter(|cursor| self.kind(*cursor) == Some(TokenKind::Identifier))
1116            .map(|cursor| self.text(cursor).to_owned())
1117            .collect()
1118    }
1119
1120    /// Whether the declaration ending at `index` was marked `static`.
1121    fn is_static(&self, index: usize) -> bool {
1122        let start = index.saturating_sub(4);
1123        (start..index).any(|cursor| self.text(cursor) == "static")
1124    }
1125
1126    /// A method written directly inside a class or struct body, in languages
1127    /// that declare members without a keyword.
1128    fn braced_member(&mut self, index: usize, exported: bool) -> Option<usize> {
1129        if !self.rules.braced_members {
1130            return None;
1131        }
1132        let inside_type = self.scopes.last().is_some_and(|scope| {
1133            scope.type_body && scope.depth.is_some_and(|depth| self.depth == depth)
1134        });
1135        if !inside_type {
1136            return None;
1137        }
1138        // An annotation is not a member. `@GetMapping("/stock")` and
1139        // `[HttpGet("/health")]` configure the member written beneath them,
1140        // and reading them as declarations both invents a method and loses
1141        // the route they carry.
1142        if self.punct(index.wrapping_sub(1), "@") || self.punct(index.wrapping_sub(1), "[") {
1143            return None;
1144        }
1145        // `Type name(` declares a method; the name is the token before `(`.
1146        // Reaching a terminator first means there is no parameter list, so
1147        // this is a field rather than a method - and the loop must leave the
1148        // decision to the field path instead of giving up here.
1149        let mut cursor = index;
1150        let limit = (index + 16).min(self.tokens.len());
1151        while cursor < limit && !self.punct(cursor + 1, "(") {
1152            if self.punct(cursor, ";") || self.punct(cursor, "{") || self.punct(cursor, "=") {
1153                return self.braced_field(index, exported);
1154            }
1155            cursor += 1;
1156        }
1157        // `private String name;` declares a field: a type, a name, and no
1158        // parameter list. The line scanner recorded these, so losing them
1159        // would be a regression rather than a simplification.
1160        if cursor >= limit || !self.punct(cursor + 1, "(") {
1161            return self.braced_field(index, exported);
1162        }
1163        if self.kind(cursor) != Some(TokenKind::Identifier) {
1164            return None;
1165        }
1166        let name = self.text(cursor).to_owned();
1167        if matches!(name.as_str(), "if" | "for" | "while" | "switch" | "return") {
1168            return None;
1169        }
1170        let test_only = self.test_only_at(index);
1171        let declaration_span = self.span(index, cursor);
1172        self.record_test_only_declaration(test_only, declaration_span);
1173        self.facts.declarations.push(Declaration {
1174            name: name.clone(),
1175            kind: DeclarationKind::Method,
1176            span: declaration_span,
1177            owner: self.owner(),
1178            exported,
1179        });
1180        self.scopes.push(Scope {
1181            name,
1182            depth: None,
1183            type_body: false,
1184            test_only,
1185        });
1186        Some(cursor + 1)
1187    }
1188
1189    fn call(&mut self, index: usize) -> Option<usize> {
1190        // `modelBuilder.Entity<Order>()` is a call whose parenthesis does not
1191        // follow the name. The type argument is what an object-relational
1192        // mapper names the entity with, so it is worth reaching.
1193        let type_arguments = self.type_argument_span(index + 1);
1194        let open = index + 1 + type_arguments;
1195        if !self.punct(open, "(") {
1196            return None;
1197        }
1198        let name = self.text(index).to_owned();
1199        if matches!(
1200            name.as_str(),
1201            "if" | "for" | "while" | "switch" | "match" | "return" | "catch" | "sizeof" | "fn"
1202        ) {
1203            return None;
1204        }
1205        let receiver = (index >= 2
1206            && (self.punct(index - 1, ".") || self.punct(index - 1, ":"))
1207            && self.kind(index - 2) == Some(TokenKind::Identifier))
1208        .then(|| self.text(index - 2).to_owned());
1209        for argument in self.type_argument_names(index + 1, type_arguments) {
1210            self.facts.references.push(Reference {
1211                name: argument,
1212                kind: ReferenceKind::Uses,
1213                receiver: None,
1214                span: self.span(index, index),
1215                owner: self.owner(),
1216                string_arguments: Vec::new(),
1217                name_arguments: Vec::new(),
1218            });
1219        }
1220        let mut arguments = Vec::new();
1221        let mut scan = open + 1;
1222        let mut depth = 1_i32;
1223        let limit = (index + 256).min(self.tokens.len());
1224        while scan < limit && depth > 0 {
1225            if self.punct(scan, "(") {
1226                depth += 1;
1227            } else if self.punct(scan, ")") {
1228                depth -= 1;
1229            } else if depth == 1 && self.kind(scan) == Some(TokenKind::String) {
1230                arguments.push(self.text(scan).trim_matches(['"', '`', '\'']).to_owned());
1231            }
1232            scan += 1;
1233        }
1234        self.facts.references.push(Reference {
1235            kind: ReferenceKind::Call,
1236            name,
1237            receiver,
1238            span: self.span(index, index),
1239            owner: self.owner(),
1240            string_arguments: arguments,
1241            name_arguments: Vec::new(),
1242        });
1243        Some(index + 1)
1244    }
1245}
1246
1247#[cfg(test)]
1248mod tests {
1249    use super::extract;
1250    use crate::facts::{DeclarationKind, ImportBinding, ReferenceKind};
1251    use crate::syntax::Language;
1252
1253    fn declared(
1254        source: &str,
1255        language: Language,
1256    ) -> Vec<(String, DeclarationKind, Option<String>)> {
1257        extract(source, language)
1258            .declarations
1259            .into_iter()
1260            .map(|item| (item.name, item.kind, item.owner))
1261            .collect()
1262    }
1263
1264    #[test]
1265    fn rust_module_declarations_are_dependencies_but_inline_modules_are_not() {
1266        let source = "pub mod engine;\nmod helper;\nuse crate::engine::Driver;\n\
1267             mod inline { pub fn nested() {} }\npub fn run() { Driver::start(); }\n";
1268        let specifiers = extract(source, Language::Rust)
1269            .imports
1270            .into_iter()
1271            .map(|import| import.specifier)
1272            .collect::<Vec<_>>();
1273        assert_eq!(
1274            specifiers,
1275            ["self::engine", "self::helper", "crate::engine::Driver"],
1276            "a mod with a body defines the module here rather than including a file"
1277        );
1278    }
1279
1280    #[test]
1281    fn rust_test_attributes_classify_the_declaration_and_nested_scope() {
1282        let source = r#"
1283            fn production() {}
1284            #[cfg(not(test))]
1285            fn production_without_tests() {}
1286            #[cfg(any(test, feature = "test-support"))]
1287            #[allow(dead_code)]
1288            mod tests {
1289                #[test]
1290                fn embedded_test() {}
1291                fn helper_for_test() {}
1292            }
1293            #[tokio::test]
1294            async fn async_test() {}
1295            #[cfg(not(not(test)))]
1296            fn double_negated_test() {}
1297        "#;
1298        let facts = extract(source, Language::Rust);
1299        let test_only = |name: &str| {
1300            facts
1301                .declarations
1302                .iter()
1303                .find(|declaration| declaration.name == name)
1304                .is_some_and(|declaration| facts.declaration_is_test_only(declaration.span))
1305        };
1306        for production in ["production", "production_without_tests"] {
1307            assert!(
1308                !test_only(production),
1309                "{production} is available to production"
1310            );
1311        }
1312        for test in [
1313            "tests",
1314            "embedded_test",
1315            "helper_for_test",
1316            "async_test",
1317            "double_negated_test",
1318        ] {
1319            assert!(test_only(test), "{test} is test-only syntax");
1320        }
1321    }
1322
1323    #[test]
1324    fn a_character_literal_holding_a_quote_does_not_shift_the_rest_of_the_file() {
1325        // A lifetime and a character literal open the same way, so this used
1326        // to leave `"` opening a string that ran on for hundreds of lines and
1327        // swallowed every declaration after it.
1328        let source = "fn classify<'a>(head: &'a str) -> bool {\n\
1329             \x20   head.contains(['.', '\"', '+']) || head.starts_with('@')\n\
1330             }\n\
1331             mod tests {\n\
1332             \x20   use super::classify;\n\
1333             }\n";
1334        let facts = extract(source, Language::Rust);
1335        assert_eq!(
1336            facts
1337                .imports
1338                .iter()
1339                .map(|import| import.specifier.as_str())
1340                .collect::<Vec<_>>(),
1341            ["super::classify"],
1342            "the import after the quote character is still reachable"
1343        );
1344        assert!(
1345            facts
1346                .declarations
1347                .iter()
1348                .any(|item| item.name == "classify"),
1349            "the lifetime is punctuation, not an unterminated literal"
1350        );
1351    }
1352
1353    #[test]
1354    fn a_restricted_visibility_still_leads_to_the_import() {
1355        assert_eq!(
1356            extract("pub(crate) use transport::serve_stdio;\n", Language::Rust)
1357                .imports
1358                .len(),
1359            1,
1360            "the parenthesised scope after pub must not hide the use"
1361        );
1362    }
1363
1364    #[test]
1365    fn a_grouped_use_names_the_module_without_its_separator() {
1366        let import = extract("use super::support::{one as first, two};\n", Language::Rust)
1367            .imports
1368            .remove(0);
1369        assert_eq!(import.specifier, "super::support");
1370        assert_eq!(import.names, ["first", "two"]);
1371        assert_eq!(
1372            import.bindings,
1373            [
1374                ImportBinding {
1375                    imported: "one".to_owned(),
1376                    local: "first".to_owned(),
1377                },
1378                ImportBinding {
1379                    imported: "two".to_owned(),
1380                    local: "two".to_owned(),
1381                },
1382            ]
1383        );
1384    }
1385
1386    #[test]
1387    fn rust_declarations_carry_their_kind_and_visibility() {
1388        let facts = extract(
1389            "pub struct Engine;\npub(crate) fn build() {}\nfn private() {}\ntrait Run {}\n",
1390            Language::Rust,
1391        );
1392        let items = facts
1393            .declarations
1394            .iter()
1395            .map(|item| (item.name.as_str(), item.kind, item.exported))
1396            .collect::<Vec<_>>();
1397        assert!(
1398            items.contains(&("Engine", DeclarationKind::Struct, true)),
1399            "got {items:?}"
1400        );
1401        assert!(
1402            items.contains(&("build", DeclarationKind::Function, true)),
1403            "got {items:?}"
1404        );
1405        assert!(
1406            items.contains(&("private", DeclarationKind::Function, false)),
1407            "got {items:?}"
1408        );
1409        assert!(
1410            items.contains(&("Run", DeclarationKind::Trait, true)),
1411            "got {items:?}"
1412        );
1413    }
1414
1415    #[test]
1416    fn rust_function_pointer_types_do_not_invent_declarations_or_calls() {
1417        let facts = extract(
1418            "type Callback = unsafe extern \"system\" fn(\n\
1419             \x20   *mut c_void,\n\
1420             \x20   *const u16,\n\
1421             ) -> *mut c_void;\n",
1422            Language::Rust,
1423        );
1424        assert!(
1425            facts
1426                .declarations
1427                .iter()
1428                .any(|item| item.name == "Callback" && item.kind == DeclarationKind::TypeAlias),
1429            "the actual alias must survive, got {:?}",
1430            facts.declarations
1431        );
1432        for false_positive in ["mut", "const", "u16", "c_void"] {
1433            assert!(
1434                !facts
1435                    .declarations
1436                    .iter()
1437                    .any(|item| item.name == false_positive),
1438                "{false_positive} is part of a pointer type, got {:?}",
1439                facts.declarations
1440            );
1441        }
1442        assert!(
1443            !facts
1444                .references
1445                .iter()
1446                .any(|reference| reference.name == "fn" && reference.kind == ReferenceKind::Call),
1447            "the function-pointer marker is a type, not a call"
1448        );
1449    }
1450
1451    #[test]
1452    fn go_groups_imports_and_capitalisation_marks_export() {
1453        let source = "package main\n\nimport (\n\tf \"fmt\"\n\t\"edgehawk.com/app/reader\"\n)\n\n\
1454             func Exported() {}\nfunc internal() {}\n";
1455        let facts = extract(source, Language::Go);
1456        assert_eq!(
1457            facts
1458                .imports
1459                .iter()
1460                .map(|import| import.specifier.as_str())
1461                .collect::<Vec<_>>(),
1462            ["fmt", "edgehawk.com/app/reader"],
1463            "a grouped import block yields one fact per path"
1464        );
1465        assert_eq!(
1466            facts.imports[0].bindings,
1467            [ImportBinding {
1468                imported: "fmt".to_owned(),
1469                local: "f".to_owned(),
1470            }]
1471        );
1472        assert_eq!(
1473            facts.imports[1].bindings,
1474            [ImportBinding {
1475                imported: "reader".to_owned(),
1476                local: "reader".to_owned(),
1477            }]
1478        );
1479        let items = facts
1480            .declarations
1481            .iter()
1482            .map(|item| (item.name.as_str(), item.exported))
1483            .collect::<Vec<_>>();
1484        assert!(items.contains(&("Exported", true)), "got {items:?}");
1485        assert!(items.contains(&("internal", false)), "got {items:?}");
1486    }
1487
1488    #[test]
1489    fn go_const_and_var_groups_declare_each_line() {
1490        let source = r#"package main
1491const (
1492    EventAdd = "added"
1493    eventDelete = "deleted"
1494)
1495var (
1496    endpoint = flag.String("endpoint", "/events", "endpoint")
1497    topics = []string{EventAdd, eventDelete}
1498)
1499"#;
1500        let facts = extract(source, Language::Go);
1501        let items = facts
1502            .declarations
1503            .iter()
1504            .map(|item| (item.name.as_str(), item.kind, item.span.line))
1505            .collect::<Vec<_>>();
1506        for expected in [
1507            ("EventAdd", DeclarationKind::Constant, 3),
1508            ("eventDelete", DeclarationKind::Constant, 4),
1509            ("endpoint", DeclarationKind::Variable, 7),
1510            ("topics", DeclarationKind::Variable, 8),
1511        ] {
1512            assert!(
1513                items.contains(&expected),
1514                "missing {expected:?}; got {items:?}"
1515            );
1516        }
1517    }
1518
1519    #[test]
1520    fn go_grouped_initializers_keep_their_call_references() {
1521        let facts = extract(
1522            "package main\nvar (\n flagName = config.String(\"name\")\n)\n",
1523            Language::Go,
1524        );
1525        assert!(
1526            facts
1527                .declarations
1528                .iter()
1529                .any(|item| item.name == "flagName" && item.kind == DeclarationKind::Variable),
1530            "the grouped declaration must survive"
1531        );
1532        assert!(
1533            facts.references.iter().any(|reference| {
1534                reference.name == "String"
1535                    && reference.kind == ReferenceKind::Call
1536                    && reference.receiver.as_deref() == Some("config")
1537            }),
1538            "the initializer call must survive, got {:?}",
1539            facts.references
1540        );
1541    }
1542
1543    #[test]
1544    fn go_grouped_values_do_not_turn_continuation_lines_into_declarations() {
1545        let source = r#"package main
1546var (
1547    config = Config{
1548        Name: "primary",
1549    }
1550    continued =
1551        buildValue
1552    next = 1
1553)
1554"#;
1555        let facts = extract(source, Language::Go);
1556        let names = facts
1557            .declarations
1558            .iter()
1559            .map(|item| item.name.as_str())
1560            .collect::<Vec<_>>();
1561        for expected in ["config", "continued", "next"] {
1562            assert!(
1563                names.contains(&expected),
1564                "missing {expected}; got {names:?}"
1565            );
1566        }
1567        for false_positive in ["Name", "buildValue"] {
1568            assert!(
1569                !names.contains(&false_positive),
1570                "{false_positive} is an initializer expression, got {names:?}"
1571            );
1572        }
1573    }
1574
1575    #[test]
1576    fn a_large_go_group_does_not_swallow_following_functions() {
1577        use std::fmt::Write as _;
1578
1579        let mut source = String::from("package main\nvar (\n");
1580        for index in 0..1_100 {
1581            let _ = writeln!(source, "value{index} = {index}");
1582        }
1583        source.push_str(")\nfunc AfterGroup() {}\n");
1584        let facts = extract(&source, Language::Go);
1585        assert!(
1586            facts
1587                .declarations
1588                .iter()
1589                .any(|item| item.name == "AfterGroup" && item.kind == DeclarationKind::Function),
1590            "the closing group delimiter must return scanning to the following function"
1591        );
1592    }
1593
1594    #[test]
1595    fn java_methods_belong_to_their_class() {
1596        let source = "package com.x;\nimport com.x.Helper;\n\
1597             public class Service {\n\
1598             \x20 private final Helper helper = null;\n\
1599             \x20 public void run() {\n\
1600             \x20   items.forEach(item -> {});\n\
1601             \x20 }\n\
1602             \x20 private int score(String value) { return 1; }\n\
1603             }\n";
1604        let items = declared(source, Language::Java);
1605        assert!(
1606            items.iter().any(|(name, kind, owner)| name == "Service"
1607                && *kind == DeclarationKind::Class
1608                && owner.is_none()),
1609            "got {items:?}"
1610        );
1611        for method in ["run", "score"] {
1612            assert!(
1613                items.iter().any(|(name, kind, owner)| name == method
1614                    && *kind == DeclarationKind::Method
1615                    && owner.as_deref() == Some("Service")),
1616                "{method} must be a method of Service, got {items:?}"
1617            );
1618        }
1619        assert!(
1620            !items.iter().any(|(name, ..)| name == "forEach"),
1621            "a call chain inside a body is not a declaration, got {items:?}"
1622        );
1623    }
1624
1625    #[test]
1626    fn java_route_annotations_keep_their_structural_owner_and_literal() {
1627        let source = "@RequestMapping(\"warehouse\")\n\
1628             public class Service {\n\
1629             \x20 @GetMapping(\"/stock\")\n\
1630             \x20 public void stock() {}\n\
1631             }\n";
1632        let facts = extract(source, Language::Java);
1633        let class_mapping = facts
1634            .calls()
1635            .find(|call| call.name == "RequestMapping")
1636            .expect("class mapping call");
1637        assert_eq!(class_mapping.owner, None);
1638        assert_eq!(class_mapping.string_arguments, ["warehouse"]);
1639
1640        let method_mapping = facts
1641            .calls()
1642            .find(|call| call.name == "GetMapping")
1643            .expect("method mapping call");
1644        assert_eq!(method_mapping.owner.as_deref(), Some("Service"));
1645        assert_eq!(method_mapping.string_arguments, ["/stock"]);
1646    }
1647
1648    #[test]
1649    fn solidity_contracts_own_their_functions_and_name_their_dependencies() {
1650        let source = "// SPDX-License-Identifier: MIT\n\
1651             pragma solidity ^0.8.20;\n\
1652             import \"./Ownable.sol\";\n\
1653             import {IERC20, SafeMath} from \"@openzeppelin/contracts/token/IERC20.sol\";\n\
1654             \n\
1655             contract Vault is Ownable {\n\
1656             \x20 event Deposited(address indexed who, uint256 amount);\n\
1657             \x20 function deposit(uint256 amount) public payable {\n\
1658             \x20   token.transferFrom(msg.sender, address(this), amount);\n\
1659             \x20 }\n\
1660             \x20 function _sweep() internal {}\n\
1661             }\n";
1662        let facts = extract(source, Language::Solidity);
1663        assert_eq!(
1664            facts
1665                .imports
1666                .iter()
1667                .map(|import| import.specifier.as_str())
1668                .collect::<Vec<_>>(),
1669            ["./Ownable.sol", "@openzeppelin/contracts/token/IERC20.sol"],
1670            "the names listed before `from` are bindings, not the path"
1671        );
1672        let items = declared(source, Language::Solidity);
1673        assert!(
1674            items.iter().any(|(name, kind, owner)| name == "Vault"
1675                && *kind == DeclarationKind::Class
1676                && owner.is_none()),
1677            "got {items:?}"
1678        );
1679        for method in ["deposit", "_sweep"] {
1680            assert!(
1681                items.iter().any(|(name, kind, owner)| name == method
1682                    && *kind == DeclarationKind::Function
1683                    && owner.as_deref() == Some("Vault")),
1684                "{method} belongs to Vault, got {items:?}"
1685            );
1686        }
1687        assert!(
1688            facts.references.iter().any(
1689                |call| call.name == "transferFrom" && call.receiver.as_deref() == Some("token")
1690            ),
1691            "a call through a receiver keeps the receiver"
1692        );
1693    }
1694
1695    #[test]
1696    fn swift_members_belong_to_the_type_their_extension_names() {
1697        let source = "import Foundation\n\
1698             import UIKit\n\
1699             \n\
1700             public struct Engine {\n\
1701             \x20 let name: String\n\
1702             \x20 public func start() { boot() }\n\
1703             }\n\
1704             \n\
1705             extension Engine {\n\
1706             \x20 func restart() { start() }\n\
1707             }\n\
1708             \n\
1709             private func boot() {}\n";
1710        let facts = extract(source, Language::Swift);
1711        assert_eq!(
1712            facts
1713                .imports
1714                .iter()
1715                .map(|import| import.specifier.as_str())
1716                .collect::<Vec<_>>(),
1717            ["Foundation", "UIKit"]
1718        );
1719        let items = declared(source, Language::Swift);
1720        assert!(
1721            items.iter().any(|(name, kind, owner)| name == "start"
1722                && *kind == DeclarationKind::Function
1723                && owner.as_deref() == Some("Engine")),
1724            "got {items:?}"
1725        );
1726        assert!(
1727            items
1728                .iter()
1729                .any(|(name, _, owner)| name == "restart" && owner.as_deref() == Some("Engine")),
1730            "an extension names what its members belong to, got {items:?}"
1731        );
1732        assert!(
1733            !items.iter().any(|(name, ..)| name == "extension"),
1734            "and declares nothing itself, got {items:?}"
1735        );
1736        assert!(
1737            items
1738                .iter()
1739                .any(|(name, _, owner)| name == "boot" && owner.is_none()),
1740            "the file-level function is back outside, got {items:?}"
1741        );
1742    }
1743
1744    #[test]
1745    fn a_rust_impl_gives_its_methods_an_owner() {
1746        let source = "struct Engine;\n\
1747             impl Engine {\n\
1748             \x20   pub fn start(&self) {}\n\
1749             }\n\
1750             impl Display for Engine {\n\
1751             \x20   fn fmt(&self) {}\n\
1752             }\n";
1753        let items = declared(source, Language::Rust);
1754        for method in ["start", "fmt"] {
1755            assert!(
1756                items
1757                    .iter()
1758                    .any(|(name, _, owner)| name == method && owner.as_deref() == Some("Engine")),
1759                "{method} belongs to Engine, not to the trait, got {items:?}"
1760            );
1761        }
1762    }
1763
1764    #[test]
1765    fn c_functions_are_declarations_rather_than_calls_to_themselves() {
1766        // No keyword introduces a C function, so every definition fell through
1767        // to the call path: the graph gained a self-edge and lost the
1768        // declaration that dead-code analysis looks for.
1769        let source = "#include <stdio.h>\n\
1770             int add(int a, int b) { return a + b; }\n\
1771             static void run(void) { add(1, 2); }\n\
1772             int main(void) { run(); return 0; }\n";
1773        let facts = extract(source, Language::C);
1774        let declared = facts
1775            .declarations
1776            .iter()
1777            .map(|item| (item.name.as_str(), item.kind, item.exported))
1778            .collect::<Vec<_>>();
1779        assert_eq!(
1780            declared,
1781            [
1782                ("add", DeclarationKind::Function, true),
1783                ("run", DeclarationKind::Function, false),
1784                ("main", DeclarationKind::Function, true),
1785            ],
1786            "a static function is file-local; the rest are linkable"
1787        );
1788        assert_eq!(
1789            facts
1790                .references
1791                .iter()
1792                .map(|reference| reference.name.as_str())
1793                .collect::<Vec<_>>(),
1794            ["add", "run"],
1795            "only the two real call sites, and no definition among them"
1796        );
1797    }
1798
1799    #[test]
1800    fn an_include_does_not_swallow_the_line_beneath_it() {
1801        let facts = extract(
1802            "#include <stdio.h>\nint first(void) { return 0; }\n",
1803            Language::C,
1804        );
1805        assert_eq!(
1806            facts.imports.len(),
1807            1,
1808            "the include is one dependency, not a run-on statement"
1809        );
1810        assert!(
1811            facts.declarations.iter().any(|item| item.name == "first"),
1812            "the function under the include survives, got {:?}",
1813            facts.declarations
1814        );
1815    }
1816
1817    #[test]
1818    fn an_out_of_line_cpp_definition_belongs_to_its_class() {
1819        let facts = extract(
1820            "int helper(int x) { return x; }\nvoid Engine::start() { helper(1); }\n",
1821            Language::Cpp,
1822        );
1823        let start = facts
1824            .declarations
1825            .iter()
1826            .find(|item| item.name == "start")
1827            .expect("the qualified definition is a declaration");
1828        assert_eq!(start.kind, DeclarationKind::Method);
1829        assert_eq!(start.owner.as_deref(), Some("Engine"));
1830    }
1831
1832    #[test]
1833    fn a_type_argument_reaches_the_name_a_mapper_configures() {
1834        // An object-relational mapper names the entity in the type argument
1835        // and the table in the string one, so a framework layer needs both to
1836        // connect them - and the parenthesis does not follow the name.
1837        let facts = extract(
1838            "modelBuilder.Entity<Order>().ToTable(\"orders\");\nif (a < b && c > d) {}\n",
1839            Language::CSharp,
1840        );
1841        let seen = facts
1842            .references
1843            .iter()
1844            .map(|reference| {
1845                (
1846                    reference.name.as_str(),
1847                    reference.kind,
1848                    reference.string_arguments.clone(),
1849                )
1850            })
1851            .collect::<Vec<_>>();
1852        assert!(
1853            seen.contains(&("Order", ReferenceKind::Uses, Vec::new())),
1854            "the entity type is reachable, got {seen:?}"
1855        );
1856        assert!(
1857            seen.contains(&("ToTable", ReferenceKind::Call, vec!["orders".to_owned()])),
1858            "and so is the table it maps to, got {seen:?}"
1859        );
1860        assert!(
1861            !seen.iter().any(|(name, ..)| *name == "b"),
1862            "a comparison is not a type argument list, got {seen:?}"
1863        );
1864    }
1865
1866    #[test]
1867    fn a_control_structure_is_not_a_function_definition() {
1868        let source = "int pick(int x) {\n\
1869             \x20 if (x) { return 1; }\n\
1870             \x20 else if (x > 2) { return 2; }\n\
1871             \x20 while (x) { x--; }\n\
1872             \x20 return helper(x);\n\
1873             }\n";
1874        let names = declared(source, Language::C)
1875            .into_iter()
1876            .map(|(name, ..)| name)
1877            .collect::<Vec<_>>();
1878        assert_eq!(
1879            names,
1880            ["pick"],
1881            "an else-if has an identifier before it and still declares nothing"
1882        );
1883    }
1884
1885    #[test]
1886    fn a_comment_never_declares_anything_in_any_brace_language() {
1887        for (source, language) in [
1888            (
1889                "// pub fn ghost() {}\n/* struct Ghost; */\npub fn real() {}\n",
1890                Language::Rust,
1891            ),
1892            ("// func Ghost() {}\nfunc Real() {}\n", Language::Go),
1893        ] {
1894            let items = declared(source, language);
1895            assert_eq!(
1896                items.len(),
1897                1,
1898                "only the real declaration counts: {items:?}"
1899            );
1900        }
1901    }
1902}