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