Skip to main content

supercov_engine/
source_units.rs

1//! Declaration units and comment-blind digests of one source file.
2//!
3//! An assertion flow rests on specific code, not on the bytes of a file, and a
4//! test executes specific declarations, not a file. This module names what a
5//! file declares -- functions, methods, classes, in a tree -- and digests each
6//! declaration with comments blanked and nested declarations replaced by a
7//! placeholder, so an edit is attributed to the declaration it lands in and to
8//! nothing else. A test's recorded execution is expressed in the same units.
9//!
10//! Comments are erased, each with the formatting that was its own: the line it
11//! occupied, or the spaces that set it off from code. Blank lines and trailing
12//! whitespace go too, outside string literals, where no language reads them.
13//! Whitespace that belongs to code is kept exactly: indentation is syntax in
14//! Python, and a line break decides a statement in JavaScript, Go, Ruby and
15//! Kotlin. A comment the language or a tool reads -- `//go:embed`, a Ruby
16//! magic comment, a Rust doctest -- is kept as well; erasing it would hide a
17//! change that runs.
18//!
19//! Nothing here is a dependency analysis. The tree says where an edit landed;
20//! whether that edit matters to a test is decided by what the test executed.
21
22use serde::{Deserialize, Serialize};
23use sha2::{Digest, Sha256};
24use std::collections::BTreeMap;
25
26/// One declaration, positioned the way anchors are: one-based lines and
27/// one-based UTF-8 byte columns, end exclusive.
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(rename_all = "camelCase", deny_unknown_fields)]
30pub struct Unit {
31    /// Name path from the file's top level -- `Server.start`, `tests::it_works`
32    /// -- and empty for the file itself. Two declarations that would share a
33    /// path are told apart by `#2`, `#3` in source order.
34    pub path: String,
35    pub kind: String,
36    pub line: usize,
37    pub column: usize,
38    pub end_line: usize,
39    pub end_column: usize,
40    /// Digest of this declaration's own text: comments blanked, each nested
41    /// declaration reduced to one placeholder character.
42    pub digest: String,
43    #[serde(default, skip_serializing_if = "Option::is_none")]
44    pub parent: Option<usize>,
45    /// A declaration with no runtime presence: a TypeScript interface, type
46    /// alias, overload signature or ambient declaration. It has a digest, so a
47    /// report can name it, but it takes no part in what a program does.
48    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
49    pub inert: bool,
50}
51
52/// What a parser could say about one file.
53#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
54#[serde(rename_all = "camelCase", deny_unknown_fields)]
55pub struct Code {
56    /// Digest of everything a program can observe: the whole file with
57    /// comments blanked and inert declarations removed.
58    pub semantic: String,
59    /// Digest of the set of declarations -- kind and path -- that can take
60    /// part in name resolution or dispatch. Adding, removing or renaming one
61    /// changes it; editing a body does not.
62    pub structure: String,
63    /// In source order; `units[0]` is the file itself and contains everything.
64    pub units: Vec<Unit>,
65    /// Lines (one-based, ascending) that hold nothing a program can observe: a
66    /// comment on its own, or blank. Counting lines without them gives a
67    /// position that inserting either does not move.
68    #[serde(default, skip_serializing_if = "Vec::is_empty")]
69    pub erased: Vec<usize>,
70}
71
72impl Unit {
73    /// A unit that can only be used by running it. A class, object, type or
74    /// module is also data -- a field, a constant, a shape -- that code reads
75    /// without a probe firing inside it, so those are never excluded from what
76    /// a test depends on.
77    pub fn is_code(&self) -> bool {
78        matches!(
79            self.kind.as_str(),
80            "function" | "method" | "constructor" | "get" | "set" | "init"
81        )
82    }
83    pub fn contains(&self, line: usize, column: usize) -> bool {
84        (self.line, self.column) <= (line, column)
85            && (line, column) < (self.end_line, self.end_column)
86    }
87    /// `path (line N)`, or `top level` for the file unit.
88    pub fn describe(&self) -> String {
89        if self.path.is_empty() {
90            "top level".to_owned()
91        } else {
92            format!("{} (line {})", self.path, self.line)
93        }
94    }
95}
96
97impl Code {
98    /// A line's number counting only lines that hold code.
99    pub fn code_line(&self, line: usize) -> usize {
100        line - self.erased.partition_point(|e| *e < line)
101    }
102    /// The innermost unit holding a position; the file unit when nothing
103    /// narrower does.
104    pub fn unit_at(&self, line: usize, column: usize) -> usize {
105        // Units are in source order with a parent before its children, so the
106        // last one starting at or before the position is either the innermost
107        // holder or a sibling that ended earlier; its ancestors settle which.
108        let mut index = self
109            .units
110            .partition_point(|u| (u.line, u.column) <= (line, column))
111            .saturating_sub(1);
112        while !self.units[index].contains(line, column) {
113            match self.units[index].parent {
114                Some(parent) => index = parent,
115                None => return 0,
116            }
117        }
118        index
119    }
120    /// A unit and everything it sits inside, innermost first, ending at the
121    /// file unit.
122    pub fn ancestors(&self, index: usize) -> impl Iterator<Item = usize> + '_ {
123        std::iter::successors(Some(index), move |i| self.units[*i].parent)
124    }
125    /// Units by path, for matching one capture against another.
126    pub fn by_path(&self) -> BTreeMap<&str, &Unit> {
127        self.units.iter().map(|u| (u.path.as_str(), u)).collect()
128    }
129    /// What moved between two views of the same file, by declaration path.
130    pub fn diff(&self, new: &Code) -> Diff {
131        let before = self.by_path();
132        let after = new.by_path();
133        Diff {
134            changed: self
135                .units
136                .iter()
137                .enumerate()
138                .filter(|(_, u)| {
139                    after
140                        .get(u.path.as_str())
141                        .is_some_and(|n| n.digest != u.digest)
142                })
143                .map(|(i, _)| i)
144                .collect(),
145            removed: self
146                .units
147                .iter()
148                .enumerate()
149                .filter(|(_, u)| !after.contains_key(u.path.as_str()))
150                .map(|(i, _)| i)
151                .collect(),
152            added: new
153                .units
154                .iter()
155                .enumerate()
156                .filter(|(_, u)| !before.contains_key(u.path.as_str()))
157                .map(|(i, _)| i)
158                .collect(),
159            structural: self.structure != new.structure,
160        }
161    }
162}
163
164/// Indices into the old view for what changed or went, into the new for what
165/// arrived.
166#[derive(Debug, Clone, PartialEq, Eq)]
167pub struct Diff {
168    pub changed: Vec<usize>,
169    pub removed: Vec<usize>,
170    pub added: Vec<usize>,
171    pub structural: bool,
172}
173impl Diff {
174    /// Nothing but declaration bodies changed, and only in units that are
175    /// code with a probe of their own -- the change can reach a test only by
176    /// being run, so a test that ran none of it is untouched.
177    pub fn narrow(&self, old: &Code, probed: &[usize]) -> bool {
178        !self.structural
179            && self.added.is_empty()
180            && self.removed.is_empty()
181            && self
182                .changed
183                .iter()
184                .all(|i| old.units[*i].is_code() && probed.contains(i))
185    }
186}
187
188/// A few names, and how many more there are.
189pub fn named<'u>(units: impl IntoIterator<Item = &'u Unit>) -> String {
190    let names = units.into_iter().map(Unit::describe).collect::<Vec<_>>();
191    match names.len() {
192        0 => "nothing".to_owned(),
193        1..=4 => names.join(", "),
194        n => format!("{}, and {} more", names[..3].join(", "), n - 3),
195    }
196}
197
198/// One declaration the parser found, in bytes.
199struct Item {
200    name: String,
201    kind: &'static str,
202    start: usize,
203    end: usize,
204    inert: bool,
205}
206struct Outline {
207    comments: Vec<(usize, usize)>,
208    /// String literals: whitespace inside them is content, not formatting.
209    strings: Vec<(usize, usize)>,
210    declarations: Vec<Item>,
211    separator: &'static str,
212}
213
214/// The parser's view of a file, or `None` when Supercov has no parser for it
215/// or the file does not parse. A file without a view is compared by its bytes.
216pub fn code(path: &str, source: &str) -> Option<Code> {
217    let extension = path.rsplit_once('.').map_or("", |(_, e)| e);
218    let outline = match extension {
219        "js" | "mjs" | "cjs" | "jsx" | "ts" | "mts" | "cts" | "tsx" => javascript(path, source),
220        "py" => python(source),
221        "rs" => rust(source),
222        "rb" => ruby(source),
223        "go" => go(source),
224        "java" => java(source),
225        "kt" | "kts" => kotlin(source),
226        _ => return None,
227    }?;
228    Some(build(source, outline))
229}
230
231fn short_digest(text: &str) -> String {
232    let digest = Sha256::digest(text.as_bytes());
233    digest[..16].iter().map(|b| format!("{b:02x}")).collect()
234}
235
236/// Comments any language keeps: Supercov's own pragmas.
237fn universally_significant(text: &str) -> bool {
238    text.to_ascii_lowercase().contains("supercov")
239}
240
241/// What to take out for one comment so that the text reads as if the comment
242/// had never been written: the whole line when the comment owns it, the
243/// whitespace that set it off from code otherwise, and a single space where
244/// removing it would glue two words together.
245fn erasure(source: &str, start: usize, end: usize) -> (usize, usize, &'static str) {
246    let bytes = source.as_bytes();
247    let blank = |b: u8| matches!(b, b' ' | b'\t' | b'\r');
248    let word = |b: u8| b.is_ascii_alphanumeric() || b == b'_' || b == b'$';
249    let line_start = source[..start].rfind('\n').map_or(0, |i| i + 1);
250    let mut after = end;
251    while after < bytes.len() && blank(bytes[after]) {
252        after += 1;
253    }
254    let terminated = after >= bytes.len() || bytes[after] == b'\n';
255    if terminated {
256        if bytes[line_start..start].iter().all(|b| blank(*b)) {
257            let stop = if after < bytes.len() {
258                after + 1
259            } else {
260                after
261            };
262            return (line_start, stop, "");
263        }
264        let mut before = start;
265        while before > line_start && blank(bytes[before - 1]) {
266            before -= 1;
267        }
268        return (before, after, "");
269    }
270    let preceded = start == line_start || blank(bytes[start - 1]);
271    let stop = if preceded { after } else { end };
272    let glue = start > 0 && stop < bytes.len() && word(bytes[start - 1]) && word(bytes[stop]);
273    (start, stop, if glue { " " } else { "" })
274}
275
276fn build(source: &str, outline: Outline) -> Code {
277    // The file with insignificant comments erased, and a map from every
278    // original offset to its place in that text. Declaration boundaries never
279    // fall inside a comment, so mapping them is exact.
280    let mut comments = outline.comments;
281    comments.sort_unstable();
282    let mut stripped = String::with_capacity(source.len());
283    let mut map = vec![0usize; source.len() + 1];
284    let mut cursor = 0;
285    for (start, end) in comments {
286        if end > source.len() || start >= end {
287            continue;
288        }
289        let (start, stop, replacement) = erasure(source, start, end);
290        let start = start.max(cursor);
291        if stop <= start {
292            continue;
293        }
294        stripped.push_str(&source[cursor..start]);
295        let base = stripped.len() - (start - cursor);
296        for (i, slot) in map[cursor..start].iter_mut().enumerate() {
297            *slot = base + i;
298        }
299        let here = stripped.len();
300        stripped.push_str(replacement);
301        map[start..stop].fill(here);
302        cursor = stop;
303    }
304    stripped.push_str(&source[cursor..]);
305    let base = stripped.len() - (source.len() - cursor);
306    for (i, slot) in map[cursor..=source.len()].iter_mut().enumerate() {
307        *slot = base + i;
308    }
309    // Then blank lines and trailing whitespace, outside string literals.
310    let mut strings = outline
311        .strings
312        .iter()
313        .filter(|(start, end)| start < end && *end <= source.len())
314        .map(|(start, end)| (map[*start], map[*end]))
315        .collect::<Vec<_>>();
316    strings.sort_unstable();
317    let (stripped, remap) = erase_blank(&stripped, &strings);
318    for slot in &mut map {
319        *slot = remap[*slot];
320    }
321
322    let mut declarations = outline.declarations;
323    declarations.retain(|d| d.start < d.end && d.end <= source.len());
324    declarations.sort_by(|a, b| a.start.cmp(&b.start).then(b.end.cmp(&a.end)));
325    declarations
326        .dedup_by(|later, earlier| later.start == earlier.start && later.end == earlier.end);
327
328    let lines = line_starts(source);
329    let position = |offset: usize| -> (usize, usize) {
330        let line = lines.partition_point(|s| *s <= offset);
331        (line, offset - lines[line - 1] + 1)
332    };
333    // A line nothing of which survived erasure.
334    let erased = (0..lines.len())
335        .filter(|i| {
336            let start = lines[*i];
337            let next = lines.get(i + 1).copied().unwrap_or(source.len());
338            start < next && map[start] == map[next]
339        })
340        .map(|i| i + 1)
341        .collect::<Vec<_>>();
342
343    struct Built {
344        path: String,
345        kind: &'static str,
346        start: usize,
347        end: usize,
348        parent: Option<usize>,
349        inert: bool,
350        children: Vec<usize>,
351    }
352    let mut built = vec![Built {
353        path: String::new(),
354        kind: "file",
355        start: 0,
356        end: source.len(),
357        parent: None,
358        inert: false,
359        children: vec![],
360    }];
361    let mut stack = vec![0usize];
362    let mut names: BTreeMap<(usize, String), usize> = BTreeMap::new();
363    for d in declarations {
364        while stack.len() > 1 && built[*stack.last().unwrap()].end <= d.start {
365            stack.pop();
366        }
367        let parent = *stack.last().unwrap();
368        let index = built.len();
369        let seen = names.entry((parent, d.name.clone())).or_insert(0);
370        *seen += 1;
371        let name = if *seen == 1 {
372            d.name
373        } else {
374            format!("{}#{}", d.name, *seen)
375        };
376        let path = if built[parent].path.is_empty() {
377            name
378        } else {
379            format!("{}{}{}", built[parent].path, outline.separator, name)
380        };
381        built.push(Built {
382            path,
383            kind: d.kind,
384            start: d.start,
385            end: d.end.min(built[parent].end),
386            parent: Some(parent),
387            inert: d.inert || built[parent].inert,
388            children: vec![],
389        });
390        built[parent].children.push(index);
391        stack.push(index);
392    }
393
394    let mut units = Vec::with_capacity(built.len());
395    for unit in &built {
396        let mut text = String::new();
397        let mut cursor = map[unit.start];
398        for child in unit.children.iter().map(|c| &built[*c]) {
399            let (start, end) = (map[child.start], map[child.end]);
400            if start < cursor {
401                continue;
402            }
403            text.push_str(&stripped[cursor..start]);
404            // An inert declaration leaves no mark: adding a type alias next to
405            // a function does not change what the function's file does.
406            if child.inert {
407                cursor = line_end_after(&stripped, end);
408            } else {
409                text.push('\u{0}');
410                cursor = end;
411            }
412        }
413        text.push_str(&stripped[cursor..map[unit.end]]);
414        let (line, column) = position(unit.start);
415        let (end_line, end_column) = position(unit.end);
416        units.push(Unit {
417            path: unit.path.clone(),
418            kind: unit.kind.to_owned(),
419            line,
420            column,
421            end_line,
422            end_column,
423            digest: short_digest(&text),
424            parent: unit.parent,
425            inert: unit.inert,
426        });
427    }
428
429    let mut semantic = String::with_capacity(stripped.len());
430    let mut cursor = 0;
431    for unit in built.iter().filter(|u| u.inert) {
432        let (start, end) = (map[unit.start], map[unit.end]);
433        if start < cursor {
434            continue;
435        }
436        semantic.push_str(&stripped[cursor..start]);
437        cursor = line_end_after(&stripped, end);
438    }
439    semantic.push_str(&stripped[cursor..]);
440
441    let mut structure = built
442        .iter()
443        .skip(1)
444        .filter(|u| !u.inert)
445        .map(|u| format!("{}\u{1}{}\n", u.kind, u.path))
446        .collect::<Vec<_>>();
447    structure.sort_unstable();
448
449    Code {
450        semantic: short_digest(&semantic),
451        structure: short_digest(&structure.concat()),
452        units,
453        erased,
454    }
455}
456
457/// The end of the line a removed declaration sat on, when nothing but
458/// whitespace follows it there; otherwise the position itself.
459fn line_end_after(text: &str, position: usize) -> usize {
460    let bytes = text.as_bytes();
461    let mut p = position;
462    while p < bytes.len() && matches!(bytes[p], b' ' | b'\t' | b'\r') {
463        p += 1;
464    }
465    if p < bytes.len() && bytes[p] == b'\n' {
466        p + 1
467    } else {
468        position
469    }
470}
471
472/// Blank lines and trailing whitespace taken out of `text`, except inside the
473/// given (sorted) string ranges, with a map from every offset of `text` to its
474/// place in the result.
475fn erase_blank(text: &str, strings: &[(usize, usize)]) -> (String, Vec<usize>) {
476    let bytes = text.as_bytes();
477    let blank = |b: u8| matches!(b, b' ' | b'\t' | b'\r');
478    let inside = |start: usize, end: usize| {
479        let i = strings.partition_point(|(_, e)| *e <= start);
480        strings.get(i).is_some_and(|(s, _)| *s < end)
481    };
482    let mut out = String::with_capacity(text.len());
483    let mut map = vec![0usize; text.len() + 1];
484    let mut cursor = 0;
485    let mut line_start = 0;
486    while line_start <= bytes.len() {
487        let line_end = text[line_start..]
488            .find('\n')
489            .map_or(bytes.len(), |i| line_start + i);
490        let next = if line_end < bytes.len() {
491            line_end + 1
492        } else {
493            bytes.len()
494        };
495        let content_end = {
496            let mut e = line_end;
497            while e > line_start && blank(bytes[e - 1]) {
498                e -= 1;
499            }
500            e
501        };
502        // What to drop: the whole line when it is blank, its trailing
503        // whitespace otherwise -- unless a string literal owns that stretch.
504        let (drop_start, drop_end) = if content_end == line_start {
505            (line_start, next)
506        } else {
507            (content_end, line_end)
508        };
509        let dropping = drop_start < drop_end && !inside(drop_start, drop_end);
510        let keep_until = if dropping { drop_start } else { next };
511        out.push_str(&text[cursor..keep_until]);
512        let base = out.len() - (keep_until - cursor);
513        for (i, slot) in map[cursor..keep_until].iter_mut().enumerate() {
514            *slot = base + i;
515        }
516        if dropping {
517            map[drop_start..drop_end].fill(out.len());
518            if drop_end < next {
519                // A trailing-whitespace drop keeps the newline.
520                out.push_str(&text[drop_end..next]);
521                let base = out.len() - (next - drop_end);
522                for (i, slot) in map[drop_end..next].iter_mut().enumerate() {
523                    *slot = base + i;
524                }
525            }
526        }
527        cursor = next;
528        if next == bytes.len() {
529            break;
530        }
531        line_start = next;
532    }
533    map[text.len()] = out.len();
534    (out, map)
535}
536
537fn line_starts(source: &str) -> Vec<usize> {
538    std::iter::once(0)
539        .chain(source.match_indices('\n').map(|(i, _)| i + 1))
540        .collect()
541}
542
543fn collapse_whitespace(text: &str) -> String {
544    text.split_whitespace().collect::<Vec<_>>().join(" ")
545}
546
547// ---------------------------------------------------------------- JavaScript
548
549fn javascript(path: &str, source: &str) -> Option<Outline> {
550    use oxc_ast::ast::*;
551    use oxc_ast_visit::{Visit, walk};
552    use oxc_span::GetSpan;
553
554    let source_type = oxc_span::SourceType::from_path(std::path::Path::new(path)).ok()?;
555    let allocator = oxc_allocator::Allocator::default();
556    let parsed = oxc_parser::Parser::new(&allocator, source, source_type).parse();
557    if parsed.panicked || !parsed.errors.is_empty() {
558        return None;
559    }
560    let comments = parsed
561        .program
562        .comments
563        .iter()
564        .map(|c| (c.span.start as usize, c.span.end as usize))
565        .filter(|(start, end)| !universally_significant(&source[*start..*end]))
566        .collect();
567
568    struct Collector<'s> {
569        source: &'s str,
570        declarations: Vec<Item>,
571        strings: Vec<(usize, usize)>,
572    }
573    impl<'s> Collector<'s> {
574        fn record(&mut self, name: String, kind: &'static str, start: u32, end: u32, inert: bool) {
575            self.declarations.push(Item {
576                name,
577                kind,
578                start: start as usize,
579                end: end as usize,
580                inert,
581            });
582        }
583        /// A function or class expression with no name of its own takes the
584        /// name it is bound to.
585        fn anonymous(expression: &Expression<'_>) -> Option<(oxc_span::Span, &'static str, bool)> {
586            match expression {
587                Expression::FunctionExpression(f) if f.id.is_none() => {
588                    Some((f.span, "function", f.body.is_none()))
589                }
590                Expression::ArrowFunctionExpression(a) => Some((a.span, "function", false)),
591                Expression::ClassExpression(c) if c.id.is_none() => {
592                    Some((c.span, "class", c.declare))
593                }
594                _ => None,
595            }
596        }
597        fn key(key: &PropertyKey<'_>) -> Option<String> {
598            match key {
599                PropertyKey::PrivateIdentifier(id) => Some(format!("#{}", id.name)),
600                _ => key.static_name().map(|n| n.into_owned()),
601            }
602        }
603    }
604    fn with_decorators(span: oxc_span::Span, decorators: &[Decorator<'_>]) -> u32 {
605        decorators
606            .iter()
607            .map(|d| d.span.start)
608            .min()
609            .map_or(span.start, |s| s.min(span.start))
610    }
611    impl<'a> Visit<'a> for Collector<'_> {
612        fn visit_string_literal(&mut self, it: &StringLiteral<'a>) {
613            self.strings
614                .push((it.span.start as usize, it.span.end as usize));
615        }
616        fn visit_template_literal(&mut self, it: &TemplateLiteral<'a>) {
617            self.strings
618                .push((it.span.start as usize, it.span.end as usize));
619            walk::walk_template_literal(self, it);
620        }
621        fn visit_ts_template_literal_type(&mut self, it: &TSTemplateLiteralType<'a>) {
622            self.strings
623                .push((it.span.start as usize, it.span.end as usize));
624            walk::walk_ts_template_literal_type(self, it);
625        }
626        fn visit_function(&mut self, it: &Function<'a>, flags: oxc_syntax::scope::ScopeFlags) {
627            if let Some(id) = &it.id {
628                self.record(
629                    id.name.to_string(),
630                    "function",
631                    it.span.start,
632                    it.span.end,
633                    it.declare || it.body.is_none(),
634                );
635            }
636            walk::walk_function(self, it, flags);
637        }
638        fn visit_class(&mut self, it: &Class<'a>) {
639            if let Some(id) = &it.id {
640                self.record(
641                    id.name.to_string(),
642                    "class",
643                    with_decorators(it.span, &it.decorators),
644                    it.span.end,
645                    it.declare,
646                );
647            }
648            walk::walk_class(self, it);
649        }
650        fn visit_method_definition(&mut self, it: &MethodDefinition<'a>) {
651            if let Some(name) = Self::key(&it.key) {
652                let kind = match it.kind {
653                    MethodDefinitionKind::Constructor => "constructor",
654                    MethodDefinitionKind::Method => "method",
655                    MethodDefinitionKind::Get => "get",
656                    MethodDefinitionKind::Set => "set",
657                };
658                self.record(
659                    name,
660                    kind,
661                    with_decorators(it.span, &it.decorators),
662                    it.span.end,
663                    it.value.body.is_none(),
664                );
665            }
666            walk::walk_method_definition(self, it);
667        }
668        fn visit_property_definition(&mut self, it: &PropertyDefinition<'a>) {
669            if let Some(value) = &it.value
670                && let Some((_, kind, inert)) = Self::anonymous(value)
671                && let Some(name) = Self::key(&it.key)
672            {
673                self.record(
674                    name,
675                    kind,
676                    with_decorators(it.span, &it.decorators),
677                    it.span.end,
678                    inert,
679                );
680            }
681            walk::walk_property_definition(self, it);
682        }
683        fn visit_object_property(&mut self, it: &ObjectProperty<'a>) {
684            if (it.method || Self::anonymous(&it.value).is_some())
685                && let Some(name) = Self::key(&it.key)
686            {
687                let kind = match &it.value {
688                    Expression::ClassExpression(_) => "class",
689                    _ => "method",
690                };
691                self.record(name, kind, it.span.start, it.span.end, false);
692            }
693            walk::walk_object_property(self, it);
694        }
695        fn visit_variable_declarator(&mut self, it: &VariableDeclarator<'a>) {
696            if let Some(init) = &it.init
697                && let Some(id) = it.id.get_binding_identifier()
698            {
699                if let Some((span, kind, inert)) = Self::anonymous(init) {
700                    self.record(id.name.to_string(), kind, span.start, span.end, inert);
701                } else if let Expression::ObjectExpression(object) = init {
702                    self.record(
703                        id.name.to_string(),
704                        "object",
705                        object.span.start,
706                        object.span.end,
707                        false,
708                    );
709                }
710            }
711            walk::walk_variable_declarator(self, it);
712        }
713        fn visit_assignment_expression(&mut self, it: &AssignmentExpression<'a>) {
714            // `exports.handle = () => {}`, `Server.prototype.start = function () {}`,
715            // `module.exports = { ... }`
716            let bound = match &it.right {
717                Expression::ObjectExpression(object) => Some((object.span, "object", false)),
718                other => Self::anonymous(other),
719            };
720            if let Some((span, kind, inert)) = bound {
721                let target =
722                    &self.source[it.left.span().start as usize..it.left.span().end as usize];
723                if !target.is_empty()
724                    && target
725                        .chars()
726                        .all(|c| c.is_alphanumeric() || matches!(c, '_' | '$' | '.' | '#'))
727                {
728                    self.record(target.to_owned(), kind, span.start, span.end, inert);
729                }
730            }
731            walk::walk_assignment_expression(self, it);
732        }
733        fn visit_ts_enum_declaration(&mut self, it: &TSEnumDeclaration<'a>) {
734            self.record(
735                it.id.name.to_string(),
736                "enum",
737                it.span.start,
738                it.span.end,
739                it.declare,
740            );
741            walk::walk_ts_enum_declaration(self, it);
742        }
743        fn visit_ts_module_declaration(&mut self, it: &TSModuleDeclaration<'a>) {
744            let name = match &it.id {
745                TSModuleDeclarationName::Identifier(id) => id.name.to_string(),
746                TSModuleDeclarationName::StringLiteral(s) => s.value.to_string(),
747            };
748            self.record(name, "namespace", it.span.start, it.span.end, it.declare);
749            walk::walk_ts_module_declaration(self, it);
750        }
751        fn visit_ts_global_declaration(&mut self, it: &TSGlobalDeclaration<'a>) {
752            self.record(
753                "global".to_owned(),
754                "namespace",
755                it.span.start,
756                it.span.end,
757                true,
758            );
759            walk::walk_ts_global_declaration(self, it);
760        }
761        fn visit_ts_interface_declaration(&mut self, it: &TSInterfaceDeclaration<'a>) {
762            self.record(
763                it.id.name.to_string(),
764                "interface",
765                it.span.start,
766                it.span.end,
767                true,
768            );
769            walk::walk_ts_interface_declaration(self, it);
770        }
771        fn visit_ts_type_alias_declaration(&mut self, it: &TSTypeAliasDeclaration<'a>) {
772            self.record(
773                it.id.name.to_string(),
774                "type",
775                it.span.start,
776                it.span.end,
777                true,
778            );
779            walk::walk_ts_type_alias_declaration(self, it);
780        }
781    }
782    let mut collector = Collector {
783        source,
784        declarations: vec![],
785        strings: vec![],
786    };
787    collector.visit_program(&parsed.program);
788    Some(Outline {
789        comments,
790        strings: collector.strings,
791        declarations: collector.declarations,
792        separator: ".",
793    })
794}
795
796// -------------------------------------------------------------------- Python
797
798fn python(source: &str) -> Option<Outline> {
799    use ruff_python_ast::{
800        Stmt,
801        visitor::{Visitor, walk_stmt},
802    };
803    use ruff_text_size::Ranged;
804
805    use ruff_python_ast::token::TokenKind;
806    let parsed = ruff_python_parser::parse_module(source).ok()?;
807    let strings = parsed
808        .tokens()
809        .iter()
810        .filter(|t| {
811            matches!(
812                t.kind(),
813                TokenKind::String | TokenKind::FStringMiddle | TokenKind::TStringMiddle
814            ) || (!matches!(
815                t.kind(),
816                TokenKind::Comment | TokenKind::Newline | TokenKind::NonLogicalNewline
817            ) && source[t.range().start().to_usize()..t.range().end().to_usize()]
818                .contains('\n'))
819        })
820        .map(|t| (t.range().start().to_usize(), t.range().end().to_usize()))
821        .collect();
822    let comments = parsed
823        .tokens()
824        .iter()
825        .filter(|t| t.kind() == TokenKind::Comment)
826        .map(|t| (t.range().start().to_usize(), t.range().end().to_usize()))
827        .filter(|(start, end)| {
828            let text = &source[*start..*end];
829            // PEP 263: the encoding declaration is read before anything else.
830            !(universally_significant(text)
831                || text.contains("coding:")
832                || text.contains("coding=")
833                || text.contains("-*-"))
834        })
835        .collect();
836
837    struct Collector(Vec<Item>);
838    impl<'a> Visitor<'a> for Collector {
839        fn visit_stmt(&mut self, stmt: &'a Stmt) {
840            match stmt {
841                Stmt::FunctionDef(def) => {
842                    let start = def
843                        .decorator_list
844                        .iter()
845                        .map(|d| d.range().start())
846                        .min()
847                        .map_or(def.range().start(), |s| s.min(def.range().start()));
848                    self.0.push(Item {
849                        name: def.name.to_string(),
850                        kind: "function",
851                        start: start.to_usize(),
852                        end: def.range().end().to_usize(),
853                        inert: false,
854                    });
855                }
856                Stmt::ClassDef(def) => {
857                    let start = def
858                        .decorator_list
859                        .iter()
860                        .map(|d| d.range().start())
861                        .min()
862                        .map_or(def.range().start(), |s| s.min(def.range().start()));
863                    self.0.push(Item {
864                        name: def.name.to_string(),
865                        kind: "class",
866                        start: start.to_usize(),
867                        end: def.range().end().to_usize(),
868                        inert: false,
869                    });
870                }
871                _ => {}
872            }
873            walk_stmt(self, stmt);
874        }
875    }
876    let mut collector = Collector(vec![]);
877    for stmt in &parsed.syntax().body {
878        collector.visit_stmt(stmt);
879    }
880    Some(Outline {
881        comments,
882        strings,
883        declarations: collector.0,
884        separator: ".",
885    })
886}
887
888// ---------------------------------------------------------------------- Rust
889
890fn rust(source: &str) -> Option<Outline> {
891    use ra_ap_syntax::{
892        AstNode, AstToken, Edition, NodeOrToken, SourceFile, SyntaxKind, ast, ast::HasName,
893    };
894
895    let parsed = SourceFile::parse(source, Edition::Edition2024);
896    if !parsed.errors().is_empty() {
897        return None;
898    }
899    let root = parsed.tree();
900    let mut comments = Vec::new();
901    let mut strings = Vec::new();
902    let mut declarations = Vec::new();
903    // A doctest is a fenced block inside a run of doc comment lines, and each
904    // line is its own token. The run is kept or blanked as one.
905    let mut doc_block: Vec<(usize, usize, bool)> = Vec::new();
906    let flush = |block: &mut Vec<(usize, usize, bool)>, comments: &mut Vec<(usize, usize)>| {
907        if !block.iter().any(|(_, _, fence)| *fence) {
908            comments.extend(block.iter().map(|(s, e, _)| (*s, *e)));
909        }
910        block.clear();
911    };
912    for element in root.syntax().descendants_with_tokens() {
913        match element {
914            NodeOrToken::Token(token) if token.kind() == SyntaxKind::COMMENT => {
915                let text = token.text();
916                let range = token.text_range();
917                let range = (
918                    u32::from(range.start()) as usize,
919                    u32::from(range.end()) as usize,
920                );
921                if universally_significant(text) {
922                    continue;
923                }
924                let doc = ast::Comment::cast(token.clone()).is_some_and(|c| c.kind().doc.is_some());
925                if doc {
926                    doc_block.push((range.0, range.1, text.contains("```")));
927                } else {
928                    flush(&mut doc_block, &mut comments);
929                    comments.push(range);
930                }
931            }
932            NodeOrToken::Token(token) if token.kind() == SyntaxKind::WHITESPACE => {}
933            NodeOrToken::Token(token) => {
934                flush(&mut doc_block, &mut comments);
935                if matches!(
936                    token.kind(),
937                    SyntaxKind::STRING | SyntaxKind::BYTE_STRING | SyntaxKind::C_STRING
938                ) || token.text().contains('\n')
939                {
940                    let range = token.text_range();
941                    strings.push((
942                        u32::from(range.start()) as usize,
943                        u32::from(range.end()) as usize,
944                    ));
945                }
946            }
947            NodeOrToken::Node(node) => {
948                let range = node.text_range();
949                let (start, end) = (
950                    u32::from(range.start()) as usize,
951                    u32::from(range.end()) as usize,
952                );
953                let named = |name: Option<ast::Name>, kind: &'static str| {
954                    name.map(|n| Item {
955                        name: n.text().to_string(),
956                        kind,
957                        start,
958                        end,
959                        inert: false,
960                    })
961                };
962                let declaration = if let Some(item) = ast::Fn::cast(node.clone()) {
963                    named(item.name(), "function")
964                } else if let Some(item) = ast::Impl::cast(node.clone()) {
965                    // `impl<T> Display for Wrapper<T>`: the header is the name.
966                    let header_end = item
967                        .assoc_item_list()
968                        .map_or(end, |l| u32::from(l.syntax().text_range().start()) as usize);
969                    let header = item
970                        .syntax()
971                        .children_with_tokens()
972                        .filter(|e| {
973                            (u32::from(e.text_range().end()) as usize) <= header_end
974                                && !matches!(e.kind(), SyntaxKind::COMMENT | SyntaxKind::ATTR)
975                        })
976                        .map(|e| e.to_string())
977                        .collect::<String>();
978                    Some(Item {
979                        name: collapse_whitespace(&header),
980                        kind: "impl",
981                        start,
982                        end,
983                        inert: false,
984                    })
985                } else if let Some(item) = ast::Struct::cast(node.clone()) {
986                    named(item.name(), "struct")
987                } else if let Some(item) = ast::Enum::cast(node.clone()) {
988                    named(item.name(), "enum")
989                } else if let Some(item) = ast::Union::cast(node.clone()) {
990                    named(item.name(), "union")
991                } else if let Some(item) = ast::Trait::cast(node.clone()) {
992                    named(item.name(), "trait")
993                } else if let Some(item) = ast::TypeAlias::cast(node.clone()) {
994                    named(item.name(), "type")
995                } else if let Some(item) = ast::Const::cast(node.clone()) {
996                    named(item.name(), "const")
997                } else if let Some(item) = ast::Static::cast(node.clone()) {
998                    named(item.name(), "static")
999                } else if let Some(item) = ast::Module::cast(node.clone()) {
1000                    named(item.name(), "mod")
1001                } else if let Some(item) = ast::MacroRules::cast(node.clone()) {
1002                    named(item.name(), "macro")
1003                } else if let Some(item) = ast::MacroDef::cast(node.clone()) {
1004                    named(item.name(), "macro")
1005                } else {
1006                    None
1007                };
1008                declarations.extend(declaration);
1009            }
1010        }
1011    }
1012    flush(&mut doc_block, &mut comments);
1013    Some(Outline {
1014        comments,
1015        strings,
1016        declarations,
1017        separator: "::",
1018    })
1019}
1020
1021// ---------------------------------------------------------------------- Ruby
1022
1023fn ruby(source: &str) -> Option<Outline> {
1024    use ruby_prism::{
1025        ClassNode, DefNode, InterpolatedRegularExpressionNode, InterpolatedStringNode,
1026        InterpolatedSymbolNode, InterpolatedXStringNode, Location, ModuleNode,
1027        RegularExpressionNode, SingletonClassNode, StringNode, SymbolNode, Visit, XStringNode,
1028    };
1029
1030    let parsed = ruby_prism::parse(source.as_bytes());
1031    if parsed.errors().next().is_some() {
1032        return None;
1033    }
1034    // Magic comments are read by the interpreter before the file runs.
1035    fn magic(text: &str) -> bool {
1036        let body = text.trim_start_matches('#').trim_start();
1037        if body.starts_with("-*-") {
1038            return true;
1039        }
1040        let Some((key, _)) = body.split_once(':') else {
1041            return false;
1042        };
1043        matches!(
1044            key.trim().to_ascii_lowercase().replace('-', "_").as_str(),
1045            "frozen_string_literal"
1046                | "encoding"
1047                | "coding"
1048                | "warn_indent"
1049                | "shareable_constant_value"
1050                | "typed"
1051        )
1052    }
1053    let comments = parsed
1054        .comments()
1055        .map(|c| (c.location().start_offset(), c.location().end_offset()))
1056        .filter(|(start, end)| {
1057            let text = &source[*start..*end];
1058            !(universally_significant(text) || magic(text))
1059        })
1060        .collect();
1061
1062    struct Collector<'s> {
1063        source: &'s str,
1064        declarations: Vec<Item>,
1065        strings: Vec<(usize, usize)>,
1066    }
1067    impl<'s> Collector<'s> {
1068        /// A heredoc's node is its opener; its body sits lines below, so the
1069        /// literal spans from the first of its parts to the last.
1070        fn literal<'pr>(&mut self, parts: impl IntoIterator<Item = Option<Location<'pr>>>) {
1071            let mut span: Option<(usize, usize)> = None;
1072            for part in parts.into_iter().flatten() {
1073                let (s, e) = (part.start_offset(), part.end_offset());
1074                span = Some(span.map_or((s, e), |(a, b)| (a.min(s), b.max(e))));
1075            }
1076            self.strings.extend(span);
1077        }
1078        fn record(&mut self, name: String, kind: &'static str, location: ruby_prism::Location<'_>) {
1079            self.declarations.push(Item {
1080                name,
1081                kind,
1082                start: location.start_offset(),
1083                end: location.end_offset(),
1084                inert: false,
1085            });
1086        }
1087        fn text(&self, location: ruby_prism::Location<'_>) -> String {
1088            collapse_whitespace(&self.source[location.start_offset()..location.end_offset()])
1089        }
1090    }
1091    impl<'pr> Visit<'pr> for Collector<'_> {
1092        fn visit_string_node(&mut self, node: &StringNode<'pr>) {
1093            self.literal([
1094                node.opening_loc(),
1095                Some(node.content_loc()),
1096                node.closing_loc(),
1097            ]);
1098        }
1099        fn visit_interpolated_string_node(&mut self, node: &InterpolatedStringNode<'pr>) {
1100            let parts = node
1101                .parts()
1102                .iter()
1103                .map(|p| Some(p.location()))
1104                .collect::<Vec<_>>();
1105            self.literal(
1106                [node.opening_loc(), node.closing_loc()]
1107                    .into_iter()
1108                    .chain(parts),
1109            );
1110            ruby_prism::visit_interpolated_string_node(self, node);
1111        }
1112        fn visit_x_string_node(&mut self, node: &XStringNode<'pr>) {
1113            self.literal([Some(node.location())]);
1114        }
1115        fn visit_interpolated_x_string_node(&mut self, node: &InterpolatedXStringNode<'pr>) {
1116            let parts = node
1117                .parts()
1118                .iter()
1119                .map(|p| Some(p.location()))
1120                .collect::<Vec<_>>();
1121            self.literal(
1122                [Some(node.opening_loc()), Some(node.closing_loc())]
1123                    .into_iter()
1124                    .chain(parts),
1125            );
1126            ruby_prism::visit_interpolated_x_string_node(self, node);
1127        }
1128        fn visit_symbol_node(&mut self, node: &SymbolNode<'pr>) {
1129            self.literal([Some(node.location())]);
1130        }
1131        fn visit_interpolated_symbol_node(&mut self, node: &InterpolatedSymbolNode<'pr>) {
1132            self.literal([Some(node.location())]);
1133            ruby_prism::visit_interpolated_symbol_node(self, node);
1134        }
1135        fn visit_regular_expression_node(&mut self, node: &RegularExpressionNode<'pr>) {
1136            self.literal([Some(node.location())]);
1137        }
1138        fn visit_interpolated_regular_expression_node(
1139            &mut self,
1140            node: &InterpolatedRegularExpressionNode<'pr>,
1141        ) {
1142            self.literal([Some(node.location())]);
1143            ruby_prism::visit_interpolated_regular_expression_node(self, node);
1144        }
1145        fn visit_def_node(&mut self, node: &DefNode<'pr>) {
1146            let name = String::from_utf8_lossy(node.name().as_slice()).into_owned();
1147            self.record(name, "method", node.location());
1148            ruby_prism::visit_def_node(self, node);
1149        }
1150        fn visit_class_node(&mut self, node: &ClassNode<'pr>) {
1151            let name = self.text(node.constant_path().location());
1152            self.record(name, "class", node.location());
1153            ruby_prism::visit_class_node(self, node);
1154        }
1155        fn visit_module_node(&mut self, node: &ModuleNode<'pr>) {
1156            let name = self.text(node.constant_path().location());
1157            self.record(name, "module", node.location());
1158            ruby_prism::visit_module_node(self, node);
1159        }
1160        fn visit_singleton_class_node(&mut self, node: &SingletonClassNode<'pr>) {
1161            let name = format!("<<{}>", self.text(node.expression().location()));
1162            self.record(name, "singleton", node.location());
1163            ruby_prism::visit_singleton_class_node(self, node);
1164        }
1165    }
1166    let mut collector = Collector {
1167        source,
1168        declarations: vec![],
1169        strings: vec![],
1170    };
1171    collector.visit(&parsed.node());
1172    Some(Outline {
1173        comments,
1174        strings: collector.strings,
1175        declarations: collector.declarations,
1176        separator: ".",
1177    })
1178}
1179
1180// ------------------------------------------------------- Go, Java and Kotlin
1181
1182/// Every node of a tree-sitter tree, in source order.
1183fn tree_nodes(tree: &tree_sitter::Tree) -> Vec<tree_sitter::Node<'_>> {
1184    let mut out = Vec::new();
1185    let mut stack = vec![tree.root_node()];
1186    while let Some(node) = stack.pop() {
1187        out.push(node);
1188        let mut cursor = node.walk();
1189        let children = node.children(&mut cursor).collect::<Vec<_>>();
1190        stack.extend(children.into_iter().rev());
1191    }
1192    out
1193}
1194
1195fn field_text<'t>(node: tree_sitter::Node<'t>, field: &str, source: &str) -> Option<String> {
1196    node.child_by_field_name(field)
1197        .map(|n| collapse_whitespace(&source[n.byte_range()]))
1198}
1199
1200fn first_child_of_kind<'t>(
1201    node: tree_sitter::Node<'t>,
1202    kind: &str,
1203) -> Option<tree_sitter::Node<'t>> {
1204    let mut cursor = node.walk();
1205    node.named_children(&mut cursor).find(|c| c.kind() == kind)
1206}
1207
1208fn go(source: &str) -> Option<Outline> {
1209    let tree = crate::go_instrumenter::parse(source).ok()?;
1210    let mut comments = Vec::new();
1211    let mut strings = Vec::new();
1212    let mut declarations = Vec::new();
1213    for node in tree_nodes(&tree) {
1214        let text = &source[node.byte_range()];
1215        match node.kind() {
1216            "interpreted_string_literal" | "raw_string_literal" | "rune_literal" => {
1217                strings.push((node.start_byte(), node.end_byte()));
1218            }
1219            "comment" => {
1220                // Directives the toolchain reads: `//go:embed`, `//go:build`,
1221                // `//export`, `//line`, and the older `// +build`.
1222                let directive = text.starts_with("//go:")
1223                    || text.starts_with("//export")
1224                    || text.starts_with("//line ")
1225                    || text.starts_with("//sys")
1226                    || text
1227                        .trim_start_matches('/')
1228                        .trim_start()
1229                        .starts_with("+build");
1230                if !directive && !universally_significant(text) {
1231                    comments.push((node.start_byte(), node.end_byte()));
1232                }
1233            }
1234            "function_declaration" => {
1235                if let Some(name) = field_text(node, "name", source) {
1236                    declarations.push(Item {
1237                        name,
1238                        kind: "function",
1239                        start: node.start_byte(),
1240                        end: node.end_byte(),
1241                        inert: false,
1242                    });
1243                }
1244            }
1245            "method_declaration" => {
1246                let receiver = node
1247                    .child_by_field_name("receiver")
1248                    .and_then(|list| first_child_of_kind(list, "parameter_declaration"))
1249                    .and_then(|p| field_text(p, "type", source))
1250                    .map(|t| {
1251                        // `*Server`, `Server[T]` -> `Server`
1252                        t.trim_start_matches(['*', '(', ' '])
1253                            .chars()
1254                            .take_while(|c| c.is_alphanumeric() || *c == '_' || *c == '.')
1255                            .collect::<String>()
1256                    });
1257                if let Some(name) = field_text(node, "name", source) {
1258                    declarations.push(Item {
1259                        name: match receiver {
1260                            Some(r) if !r.is_empty() => format!("{r}.{name}"),
1261                            _ => name,
1262                        },
1263                        kind: "method",
1264                        start: node.start_byte(),
1265                        end: node.end_byte(),
1266                        inert: false,
1267                    });
1268                }
1269            }
1270            "type_spec" | "type_alias" => {
1271                if let Some(name) = field_text(node, "name", source) {
1272                    declarations.push(Item {
1273                        name,
1274                        kind: "type",
1275                        start: node.start_byte(),
1276                        end: node.end_byte(),
1277                        inert: false,
1278                    });
1279                }
1280            }
1281            _ => {}
1282        }
1283    }
1284    Some(Outline {
1285        comments,
1286        strings,
1287        declarations,
1288        separator: ".",
1289    })
1290}
1291
1292/// `name(Type, Type)`: overloads are different declarations and are told
1293/// apart by what they take, not by the order they appear in.
1294fn with_parameters(
1295    name: String,
1296    parameters: Option<tree_sitter::Node<'_>>,
1297    source: &str,
1298) -> String {
1299    let Some(parameters) = parameters else {
1300        return name;
1301    };
1302    let mut cursor = parameters.walk();
1303    let types = parameters
1304        .named_children(&mut cursor)
1305        .filter_map(|p| {
1306            if let Some(kind) = p.child_by_field_name("type") {
1307                return Some(collapse_whitespace(&source[kind.byte_range()]));
1308            }
1309            // Kotlin `name: Type` and Java `Type... name` have no type field;
1310            // the type is the last (Kotlin) or first (Java) named child.
1311            let mut inner = p.walk();
1312            let children = p.named_children(&mut inner).collect::<Vec<_>>();
1313            let kind = if p.kind() == "spread_parameter" {
1314                children.first()
1315            } else {
1316                children.iter().find(|c| {
1317                    !matches!(
1318                        c.kind(),
1319                        "identifier"
1320                            | "simple_identifier"
1321                            | "modifiers"
1322                            | "parameter_modifiers"
1323                            | "annotation"
1324                    )
1325                })
1326            };
1327            kind.map(|k| collapse_whitespace(&source[k.byte_range()]))
1328        })
1329        .collect::<Vec<_>>();
1330    format!("{name}({})", types.join(", "))
1331}
1332
1333fn java(source: &str) -> Option<Outline> {
1334    let tree =
1335        crate::jvm_instrumenter::parse(source, crate::jvm_instrumenter::JvmLanguage::Java).ok()?;
1336    let mut comments = Vec::new();
1337    let mut strings = Vec::new();
1338    let mut declarations = Vec::new();
1339    for node in tree_nodes(&tree) {
1340        let kind = match node.kind() {
1341            "string_literal" | "character_literal" => {
1342                strings.push((node.start_byte(), node.end_byte()));
1343                continue;
1344            }
1345            "line_comment" | "block_comment" => {
1346                if !universally_significant(&source[node.byte_range()]) {
1347                    comments.push((node.start_byte(), node.end_byte()));
1348                }
1349                continue;
1350            }
1351            "class_declaration" => "class",
1352            "interface_declaration" => "interface",
1353            "enum_declaration" => "enum",
1354            "record_declaration" => "record",
1355            "annotation_type_declaration" => "annotation",
1356            "method_declaration" => "method",
1357            "constructor_declaration" => "constructor",
1358            _ => continue,
1359        };
1360        let Some(name) = field_text(node, "name", source) else {
1361            continue;
1362        };
1363        let name = if matches!(kind, "method" | "constructor") {
1364            with_parameters(name, node.child_by_field_name("parameters"), source)
1365        } else {
1366            name
1367        };
1368        declarations.push(Item {
1369            name,
1370            kind,
1371            start: node.start_byte(),
1372            end: node.end_byte(),
1373            inert: false,
1374        });
1375    }
1376    Some(Outline {
1377        comments,
1378        strings,
1379        declarations,
1380        separator: ".",
1381    })
1382}
1383
1384fn kotlin(source: &str) -> Option<Outline> {
1385    let tree = crate::jvm_instrumenter::parse(source, crate::jvm_instrumenter::JvmLanguage::Kotlin)
1386        .ok()?;
1387    let mut comments = Vec::new();
1388    let mut strings = Vec::new();
1389    let mut declarations = Vec::new();
1390    for node in tree_nodes(&tree) {
1391        let (name, kind, inert) = match node.kind() {
1392            "string_literal" | "multiline_string_literal" | "character_literal" => {
1393                strings.push((node.start_byte(), node.end_byte()));
1394                continue;
1395            }
1396            "line_comment" | "block_comment" | "multiline_comment" => {
1397                if !universally_significant(&source[node.byte_range()]) {
1398                    comments.push((node.start_byte(), node.end_byte()));
1399                }
1400                continue;
1401            }
1402            "class_declaration" => (field_text(node, "name", source), "class", false),
1403            "object_declaration" => (field_text(node, "name", source), "object", false),
1404            "companion_object" => (
1405                Some(field_text(node, "name", source).unwrap_or_else(|| "Companion".to_owned())),
1406                "object",
1407                false,
1408            ),
1409            "function_declaration" => (
1410                field_text(node, "name", source).map(|n| {
1411                    with_parameters(
1412                        n,
1413                        first_child_of_kind(node, "function_value_parameters"),
1414                        source,
1415                    )
1416                }),
1417                "function",
1418                false,
1419            ),
1420            "secondary_constructor" => (
1421                Some(with_parameters(
1422                    "constructor".to_owned(),
1423                    first_child_of_kind(node, "function_value_parameters"),
1424                    source,
1425                )),
1426                "constructor",
1427                false,
1428            ),
1429            "property_declaration" => (
1430                first_child_of_kind(node, "variable_declaration")
1431                    .and_then(|v| first_child_of_kind(v, "identifier"))
1432                    .map(|id| collapse_whitespace(&source[id.byte_range()])),
1433                "property",
1434                false,
1435            ),
1436            "anonymous_initializer" => (Some("init".to_owned()), "init", false),
1437            "type_alias" => (
1438                first_child_of_kind(node, "identifier")
1439                    .map(|id| collapse_whitespace(&source[id.byte_range()])),
1440                "type",
1441                true,
1442            ),
1443            _ => continue,
1444        };
1445        let Some(name) = name else {
1446            continue;
1447        };
1448        declarations.push(Item {
1449            name,
1450            kind,
1451            start: node.start_byte(),
1452            end: node.end_byte(),
1453            inert,
1454        });
1455    }
1456    Some(Outline {
1457        comments,
1458        strings,
1459        declarations,
1460        separator: ".",
1461    })
1462}
1463
1464#[cfg(test)]
1465mod tests {
1466    use super::*;
1467
1468    fn paths(code: &Code) -> Vec<(&str, &str)> {
1469        code.units
1470            .iter()
1471            .skip(1)
1472            .map(|u| (u.kind.as_str(), u.path.as_str()))
1473            .collect()
1474    }
1475    fn unit<'c>(code: &'c Code, path: &str) -> &'c Unit {
1476        code.units
1477            .iter()
1478            .find(|u| u.path == path)
1479            .unwrap_or_else(|| panic!("no unit {path} in {:?}", paths(code)))
1480    }
1481
1482    #[test]
1483    fn a_comment_changes_no_digest_in_any_language() {
1484        // Comments are the one thing a program cannot observe, and Supercov's
1485        // pragmas the one exception that every language shares.
1486        let cases: [(&str, &str, &str); 7] = [
1487            (
1488                "a.js",
1489                "// note\nexport function f(x) {\n  return x + 1; // why\n}\n",
1490                "// rewritten\nexport function f(x) {\n  return x + 1; /* changed */\n}\n",
1491            ),
1492            (
1493                "a.py",
1494                "# note\ndef f(x):\n    return x + 1  # why\n",
1495                "# rewritten\ndef f(x):\n    return x + 1  # changed\n",
1496            ),
1497            (
1498                "a.rs",
1499                "// note\npub fn f(x: i32) -> i32 {\n    x + 1 // why\n}\n",
1500                "/// A doc line without a fence.\npub fn f(x: i32) -> i32 {\n    x + 1 /* changed */\n}\n",
1501            ),
1502            (
1503                "a.rb",
1504                "# note\ndef f(x)\n  x + 1 # why\nend\n",
1505                "# rewritten\ndef f(x)\n  x + 1 # changed\nend\n",
1506            ),
1507            (
1508                "a.go",
1509                "package p\n\n// note\nfunc F(x int) int {\n\treturn x + 1 // why\n}\n",
1510                "package p\n\n// rewritten\nfunc F(x int) int {\n\treturn x + 1 /* changed */\n}\n",
1511            ),
1512            (
1513                "A.java",
1514                "// note\nclass A {\n  int f(int x) {\n    return x + 1; // why\n  }\n}\n",
1515                "/** Rewritten. */\nclass A {\n  int f(int x) {\n    return x + 1; /* changed */\n  }\n}\n",
1516            ),
1517            (
1518                "A.kt",
1519                "// note\nfun f(x: Int): Int {\n    return x + 1 // why\n}\n",
1520                "/* rewritten */\nfun f(x: Int): Int {\n    return x + 1 /* changed */\n}\n",
1521            ),
1522        ];
1523        for (path, before, after) in cases {
1524            let a = code(path, before).unwrap_or_else(|| panic!("{path} parses"));
1525            let b = code(path, after).unwrap();
1526            assert_eq!(a.semantic, b.semantic, "{path}: semantic digest");
1527            assert_eq!(a.structure, b.structure, "{path}: structure");
1528            assert_eq!(
1529                a.units
1530                    .iter()
1531                    .map(|u| (&u.path, &u.digest))
1532                    .collect::<Vec<_>>(),
1533                b.units
1534                    .iter()
1535                    .map(|u| (&u.path, &u.digest))
1536                    .collect::<Vec<_>>(),
1537                "{path}: unit digests"
1538            );
1539            assert!(a.units.len() >= 2, "{path}: declares something");
1540        }
1541    }
1542
1543    #[test]
1544    fn a_comment_leaves_no_trace_wherever_it_sat() {
1545        // Adding a comment line, a trailing comment or an inline comment must
1546        // read the same as never having written it.
1547        let plain = code("c.js", "function f(a, b) {\n  return a + b;\n}\n").unwrap();
1548        for commented in [
1549            "// above\nfunction f(a, b) {\n  return a + b;\n}\n",
1550            "function f(a, b) {\n  // inside, on its own line\n  return a + b;\n}\n",
1551            "function f(a, b) { // trailing\n  return a + b;   // and here\n}\n",
1552            "function f(a, /* inline */ b) {\n  return a + /* mid */ b;\n}\n",
1553            "function f(a, b) {\n  return/* glued */ a + b;\n}\n",
1554            "function f(a, b) {\n  /* leading */ return a + b;\n}\n",
1555            "function f(a, b) {\n  return a + b;\n}\n// at the end, no newline",
1556            "function f(a, b) {\n  return a + b;\n}\n\n/* a block\n   over lines */\n",
1557        ] {
1558            let with = code("c.js", commented).unwrap();
1559            assert_eq!(plain.semantic, with.semantic, "{commented:?}");
1560            assert_eq!(
1561                plain.units[1].digest, with.units[1].digest,
1562                "f's own digest: {commented:?}"
1563            );
1564            assert_eq!(plain.units[0].digest, with.units[0].digest, "{commented:?}");
1565        }
1566        let python = code("p.py", "def f():\n    a = 1\n    return a\n").unwrap();
1567        let commented = code(
1568            "p.py",
1569            "def f():\n    a = 1  # set\n    # explain\n    return a\n",
1570        )
1571        .unwrap();
1572        assert_eq!(python.semantic, commented.semantic);
1573        let string = code(
1574            "p.py",
1575            "def f():\n    return '''a\n    # not a comment\n    b'''\n",
1576        )
1577        .unwrap();
1578        let other = code("p.py", "def f():\n    return '''a\n    b'''\n").unwrap();
1579        assert_ne!(string.semantic, other.semantic, "a string is not a comment");
1580    }
1581
1582    #[test]
1583    fn blank_lines_and_trailing_whitespace_are_formatting_outside_strings() {
1584        let cases: [(&str, &str, &str); 7] = [
1585            (
1586                "b.js",
1587                "function f() {\n  return 1;\n}\n",
1588                "\nfunction f() {  \n\n  return 1;\t\n\n}\n\n",
1589            ),
1590            (
1591                "b.py",
1592                "def f():\n    a = 1\n    return a\n",
1593                "def f():\n    a = 1   \n\n    return a\n\n\n",
1594            ),
1595            (
1596                "b.rs",
1597                "fn f() -> i32 {\n    1\n}\n",
1598                "fn f() -> i32 {\n\n    1  \n}\n",
1599            ),
1600            ("b.rb", "def f\n  1\nend\n", "def f\n\n  1  \nend\n\n"),
1601            (
1602                "b.go",
1603                "package p\n\nfunc F() int {\n\treturn 1\n}\n",
1604                "package p\n\n\nfunc F() int {\n\treturn 1  \n\n}\n",
1605            ),
1606            (
1607                "B.java",
1608                "class B {\n  int f() {\n    return 1;\n  }\n}\n",
1609                "class B {\n\n  int f() {  \n    return 1;\n\n  }\n}\n",
1610            ),
1611            (
1612                "B.kt",
1613                "fun f(): Int {\n    return 1\n}\n",
1614                "fun f(): Int {\n\n    return 1   \n}\n\n",
1615            ),
1616        ];
1617        for (path, tidy, loose) in cases {
1618            let a = code(path, tidy).unwrap();
1619            let b = code(path, loose).unwrap();
1620            assert_eq!(a.semantic, b.semantic, "{path}");
1621            assert_eq!(
1622                a.units.iter().map(|u| &u.digest).collect::<Vec<_>>(),
1623                b.units.iter().map(|u| &u.digest).collect::<Vec<_>>(),
1624                "{path}"
1625            );
1626        }
1627        // Inside a literal, the same whitespace is the program's data.
1628        let literal_cases: [(&str, &str, &str); 7] = [
1629            ("s.js", "const t = `a\n\nb`;\n", "const t = `a\nb`;\n"),
1630            ("s.py", "t = '''a  \nb'''\n", "t = '''a\nb'''\n"),
1631            (
1632                "s.rs",
1633                "const T: &str = \"a\n\nb\";\n",
1634                "const T: &str = \"a\nb\";\n",
1635            ),
1636            (
1637                "s.rb",
1638                "T = <<~EOS\n  a\n\n  b\nEOS\n",
1639                "T = <<~EOS\n  a\n  b\nEOS\n",
1640            ),
1641            (
1642                "s.go",
1643                "package p\n\nconst T = `a\n\nb`\n",
1644                "package p\n\nconst T = `a\nb`\n",
1645            ),
1646            (
1647                "S.java",
1648                "class S {\n  String t = \"\"\"\n    a\n\n    b\"\"\";\n}\n",
1649                "class S {\n  String t = \"\"\"\n    a\n    b\"\"\";\n}\n",
1650            ),
1651            (
1652                "S.kt",
1653                "val t = \"\"\"a\n\nb\"\"\"\n",
1654                "val t = \"\"\"a\nb\"\"\"\n",
1655            ),
1656        ];
1657        for (path, with, without) in literal_cases {
1658            let a = code(path, with).unwrap_or_else(|| panic!("{path} parses"));
1659            let b = code(path, without).unwrap();
1660            assert_ne!(
1661                a.semantic, b.semantic,
1662                "{path}: a blank line inside a string is content"
1663            );
1664        }
1665    }
1666
1667    #[test]
1668    fn erased_lines_are_named_so_positions_can_skip_them() {
1669        let x = code(
1670            "e.js",
1671            "// one\n\nfunction f() {\n  // two\n  return 1; // not erased: code here\n\n}\n",
1672        )
1673        .unwrap();
1674        assert_eq!(x.erased, [1, 2, 4, 6]);
1675        assert_eq!(x.code_line(3), 1, "f is the first line of code");
1676        assert_eq!(x.code_line(5), 2);
1677        assert_eq!(x.code_line(7), 3);
1678    }
1679
1680    #[test]
1681    fn a_supercov_pragma_in_a_comment_is_kept() {
1682        let a = code("a.js", "// supercov: observes x\nlet x = 1;\n").unwrap();
1683        let b = code("a.js", "// supercov: observes y\nlet x = 1;\n").unwrap();
1684        assert_ne!(a.semantic, b.semantic);
1685    }
1686
1687    #[test]
1688    fn an_edit_lands_in_its_own_declaration_only() {
1689        let before =
1690            "export function a() {\n  return 1;\n}\nexport function b() {\n  return 2;\n}\n";
1691        let after =
1692            "export function a() {\n  return 1;\n}\nexport function b() {\n  return 2 + 0;\n}\n";
1693        let x = code("m.js", before).unwrap();
1694        let y = code("m.js", after).unwrap();
1695        assert_eq!(unit(&x, "a").digest, unit(&y, "a").digest, "a untouched");
1696        assert_ne!(unit(&x, "b").digest, unit(&y, "b").digest, "b edited");
1697        assert_eq!(x.units[0].digest, y.units[0].digest, "top level untouched");
1698        assert_eq!(x.structure, y.structure);
1699        assert_ne!(x.semantic, y.semantic);
1700    }
1701
1702    #[test]
1703    fn a_class_digest_excludes_its_methods_and_counts_them() {
1704        let before = "class C {\n  a() { return 1; }\n  b() { return 2; }\n}\n";
1705        let x = code("c.js", before).unwrap();
1706        assert_eq!(
1707            paths(&x),
1708            [("class", "C"), ("method", "C.a"), ("method", "C.b")]
1709        );
1710        // Editing a method body leaves the class alone.
1711        let y = code(
1712            "c.js",
1713            "class C {\n  a() { return 1; }\n  b() { return 3; }\n}\n",
1714        )
1715        .unwrap();
1716        assert_eq!(unit(&x, "C").digest, unit(&y, "C").digest);
1717        assert_eq!(x.structure, y.structure);
1718        // Adding a method changes both the class and the structure.
1719        let z = code(
1720            "c.js",
1721            "class C {\n  a() { return 1; }\n  b() { return 2; }\n  c() {}\n}\n",
1722        )
1723        .unwrap();
1724        assert_ne!(unit(&x, "C").digest, unit(&z, "C").digest);
1725        assert_ne!(x.structure, z.structure);
1726        // Renaming a method is a structural change even though the class text
1727        // around the placeholder is unchanged.
1728        let r = code(
1729            "c.js",
1730            "class C {\n  a() { return 1; }\n  d() { return 2; }\n}\n",
1731        )
1732        .unwrap();
1733        assert_eq!(unit(&x, "C").digest, unit(&r, "C").digest);
1734        assert_ne!(x.structure, r.structure);
1735    }
1736
1737    #[test]
1738    fn reordering_two_methods_changes_nothing() {
1739        let x = code(
1740            "c.js",
1741            "class C {\n  a() { return 1; }\n  b() { return 2; }\n}\n",
1742        )
1743        .unwrap();
1744        let y = code(
1745            "c.js",
1746            "class C {\n  b() { return 2; }\n  a() { return 1; }\n}\n",
1747        )
1748        .unwrap();
1749        assert_eq!(unit(&x, "C").digest, unit(&y, "C").digest);
1750        assert_eq!(unit(&x, "C.a").digest, unit(&y, "C.a").digest);
1751        assert_eq!(x.structure, y.structure);
1752    }
1753
1754    #[test]
1755    fn whitespace_is_never_blanked() {
1756        let x = code(
1757            "a.py",
1758            "def f(x):\n    if x:\n        return 1\n    return 2\n",
1759        )
1760        .unwrap();
1761        let y = code(
1762            "a.py",
1763            "def f(x):\n    if x:\n        return 1\n        return 2\n",
1764        )
1765        .unwrap();
1766        assert_ne!(unit(&x, "f").digest, unit(&y, "f").digest);
1767    }
1768
1769    #[test]
1770    fn typescript_types_are_inert() {
1771        let before =
1772            "interface A { x: number }\ntype B = A;\nexport function f(a: A): B { return a; }\n";
1773        let x = code("t.ts", before).unwrap();
1774        assert!(unit(&x, "A").inert && unit(&x, "B").inert && !unit(&x, "f").inert);
1775        // Editing, adding or removing a type changes no digest a program rests on.
1776        let y = code("t.ts", "interface A { x: number; y: string }\ntype B = A;\ntype C = B;\nexport function f(a: A): B { return a; }\n").unwrap();
1777        assert_eq!(x.semantic, y.semantic);
1778        assert_eq!(x.structure, y.structure);
1779        assert_eq!(x.units[0].digest, y.units[0].digest);
1780        assert_eq!(unit(&x, "f").digest, unit(&y, "f").digest);
1781        // An enum is a value: it counts.
1782        let z = code("t.ts", "enum E { A }\n").unwrap();
1783        assert!(!unit(&z, "E").inert);
1784        // An overload signature has no body and is inert; the implementation is not.
1785        let o = code("o.ts", "export function g(a: string): void;\nexport function g(a: number): void;\nexport function g(a: unknown) {}\n").unwrap();
1786        let g = o
1787            .units
1788            .iter()
1789            .filter(|u| u.path.starts_with('g'))
1790            .collect::<Vec<_>>();
1791        assert_eq!(g.iter().filter(|u| u.inert).count(), 2, "{:?}", paths(&o));
1792        assert_eq!(g.iter().filter(|u| !u.inert).count(), 1);
1793    }
1794
1795    #[test]
1796    fn javascript_names_what_a_function_is_bound_to() {
1797        let source = "const handle = async (req) => {};\nexports.run = function () {};\nconst api = {\n  get: () => {},\n  post() {},\n};\nclass S {\n  #secret() {}\n  static create() {}\n  get size() { return 1; }\n  field = () => {};\n}\nit('does a thing', () => {});\n";
1798        let x = code("s.js", source).unwrap();
1799        assert_eq!(
1800            paths(&x),
1801            [
1802                ("function", "handle"),
1803                ("function", "exports.run"),
1804                ("object", "api"),
1805                ("method", "api.get"),
1806                ("method", "api.post"),
1807                ("class", "S"),
1808                ("method", "S.#secret"),
1809                ("method", "S.create"),
1810                ("get", "S.size"),
1811                ("function", "S.field"),
1812            ]
1813        );
1814        // An anonymous callback is part of whatever encloses it.
1815        assert_eq!(x.unit_at(13, 1), 0);
1816    }
1817
1818    #[test]
1819    fn decorators_belong_to_what_they_decorate() {
1820        let py = code("d.py", "@app.route('/x')\ndef handler():\n    return 1\n").unwrap();
1821        assert_eq!(
1822            (unit(&py, "handler").line, unit(&py, "handler").column),
1823            (1, 1)
1824        );
1825        let py2 = code("d.py", "@app.route('/y')\ndef handler():\n    return 1\n").unwrap();
1826        assert_ne!(unit(&py, "handler").digest, unit(&py2, "handler").digest);
1827        assert_eq!(
1828            py.units[0].digest, py2.units[0].digest,
1829            "the route lives in the handler"
1830        );
1831
1832        let ts = code("d.ts", "class C {\n  @Get('/x')\n  handle() {}\n}\n").unwrap();
1833        assert_eq!(unit(&ts, "C.handle").line, 2);
1834        let java = code("D.java", "class D {\n  @Test\n  void t() {}\n}\n").unwrap();
1835        assert_eq!(unit(&java, "D.t()").line, 2);
1836        let rust = code("d.rs", "#[test]\nfn t() {}\n").unwrap();
1837        assert_eq!(unit(&rust, "t").line, 1);
1838    }
1839
1840    #[test]
1841    fn rust_paths_use_the_language_s_separator_and_name_impls_by_header() {
1842        let source = "pub struct W<T>(T);\nimpl<T: std::fmt::Debug> std::fmt::Display for W<T> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Ok(()) }\n}\nimpl<T> W<T> {\n    pub fn new(t: T) -> Self { W(t) }\n}\n#[cfg(test)]\nmod tests {\n    #[test]\n    fn it_works() {}\n}\n";
1843        let x = code("w.rs", source).unwrap();
1844        assert_eq!(
1845            paths(&x),
1846            [
1847                ("struct", "W"),
1848                (
1849                    "impl",
1850                    "impl<T: std::fmt::Debug> std::fmt::Display for W<T>"
1851                ),
1852                (
1853                    "function",
1854                    "impl<T: std::fmt::Debug> std::fmt::Display for W<T>::fmt"
1855                ),
1856                ("impl", "impl<T> W<T>"),
1857                ("function", "impl<T> W<T>::new"),
1858                ("mod", "tests"),
1859                ("function", "tests::it_works"),
1860            ]
1861        );
1862    }
1863
1864    #[test]
1865    fn a_rust_doctest_is_code_and_a_plain_doc_comment_is_not() {
1866        let a = code("l.rs", "/// Adds one.\npub fn f(x: i32) -> i32 { x + 1 }\n").unwrap();
1867        let b = code(
1868            "l.rs",
1869            "/// Adds exactly one.\npub fn f(x: i32) -> i32 { x + 1 }\n",
1870        )
1871        .unwrap();
1872        assert_eq!(unit(&a, "f").digest, unit(&b, "f").digest);
1873        let c = code(
1874            "l.rs",
1875            "/// ```\n/// assert_eq!(f(1), 2);\n/// ```\npub fn f(x: i32) -> i32 { x + 1 }\n",
1876        )
1877        .unwrap();
1878        let d = code(
1879            "l.rs",
1880            "/// ```\n/// assert_eq!(f(1), 3);\n/// ```\npub fn f(x: i32) -> i32 { x + 1 }\n",
1881        )
1882        .unwrap();
1883        assert_ne!(unit(&c, "f").digest, unit(&d, "f").digest);
1884    }
1885
1886    #[test]
1887    fn ruby_keeps_magic_comments_and_names_reopened_classes_apart() {
1888        let a = code(
1889            "m.rb",
1890            "# frozen_string_literal: true\nclass A\n  def go; end\nend\n",
1891        )
1892        .unwrap();
1893        let b = code(
1894            "m.rb",
1895            "# frozen_string_literal: false\nclass A\n  def go; end\nend\n",
1896        )
1897        .unwrap();
1898        assert_ne!(a.semantic, b.semantic, "string mutability is behaviour");
1899        let c = code("m.rb", "# a note\nclass A\n  def go; end\nend\n").unwrap();
1900        let d = code("m.rb", "# another note\nclass A\n  def go; end\nend\n").unwrap();
1901        assert_eq!(c.semantic, d.semantic);
1902        let x = code("r.rb", "module M\n  class A\n    def go; end\n    class << self\n      def make; end\n    end\n  end\nend\nclass A\n  def again; end\nend\n").unwrap();
1903        assert_eq!(
1904            paths(&x),
1905            [
1906                ("module", "M"),
1907                ("class", "M.A"),
1908                ("method", "M.A.go"),
1909                ("singleton", "M.A.<<self>"),
1910                ("method", "M.A.<<self>.make"),
1911                ("class", "A"),
1912                ("method", "A.again"),
1913            ]
1914        );
1915    }
1916
1917    #[test]
1918    fn go_keeps_directives_and_names_methods_by_receiver() {
1919        let a = code(
1920            "e.go",
1921            "package p\n\nimport _ \"embed\"\n\n//go:embed a.txt\nvar data string\n",
1922        )
1923        .unwrap();
1924        let b = code(
1925            "e.go",
1926            "package p\n\nimport _ \"embed\"\n\n//go:embed b.txt\nvar data string\n",
1927        )
1928        .unwrap();
1929        assert_ne!(
1930            a.semantic, b.semantic,
1931            "the embed directive chooses the data"
1932        );
1933        let x = code("s.go", "package p\n\ntype Server struct{}\n\nfunc (s *Server) Start() error { return nil }\n\nfunc New() *Server { return &Server{} }\n").unwrap();
1934        assert_eq!(
1935            paths(&x),
1936            [
1937                ("type", "Server"),
1938                ("method", "Server.Start"),
1939                ("function", "New")
1940            ]
1941        );
1942    }
1943
1944    #[test]
1945    fn jvm_overloads_are_named_by_their_parameters() {
1946        let java = code(
1947            "O.java",
1948            "class O {\n  void f(String s) {}\n  void f(int n, String... rest) {}\n  O() {}\n}\n",
1949        )
1950        .unwrap();
1951        assert_eq!(
1952            paths(&java),
1953            [
1954                ("class", "O"),
1955                ("method", "O.f(String)"),
1956                ("method", "O.f(int, String)"),
1957                ("constructor", "O.O()"),
1958            ]
1959        );
1960        let kotlin = code("K.kt", "class K(val x: Int) {\n    init { println(x) }\n    val y: Int get() = x\n    fun f(s: String) {}\n    fun f(n: Int) {}\n    constructor(s: String) : this(s.length)\n    companion object {\n        fun make() = K(1)\n    }\n}\ntypealias Alias = K\n").unwrap();
1961        assert_eq!(
1962            paths(&kotlin),
1963            [
1964                ("class", "K"),
1965                ("init", "K.init"),
1966                ("property", "K.y"),
1967                ("function", "K.f(String)"),
1968                ("function", "K.f(Int)"),
1969                ("constructor", "K.constructor(String)"),
1970                ("object", "K.Companion"),
1971                ("function", "K.Companion.make()"),
1972                ("type", "Alias"),
1973            ]
1974        );
1975        assert!(unit(&kotlin, "Alias").inert);
1976    }
1977
1978    #[test]
1979    fn same_named_declarations_are_numbered_in_order() {
1980        let x = code("p.py", "def f():\n    return 1\ndef f():\n    return 2\n").unwrap();
1981        assert_eq!(paths(&x), [("function", "f"), ("function", "f#2")]);
1982    }
1983
1984    #[test]
1985    fn unit_at_finds_the_innermost_holder() {
1986        let source = "const k = 1;\nclass C {\n  a() {\n    return k;\n  }\n}\nfunction b() {}\n";
1987        let x = code("u.js", source).unwrap();
1988        assert_eq!(x.units[x.unit_at(1, 1)].path, "");
1989        assert_eq!(x.units[x.unit_at(2, 1)].path, "C");
1990        assert_eq!(x.units[x.unit_at(4, 5)].path, "C.a");
1991        assert_eq!(
1992            x.units[x.unit_at(6, 1)].path,
1993            "C",
1994            "the closing brace is the class's"
1995        );
1996        assert_eq!(x.units[x.unit_at(7, 1)].path, "b");
1997        assert_eq!(x.units[x.unit_at(7, 16)].path, "", "past b's end");
1998        assert_eq!(
1999            x.ancestors(x.unit_at(4, 5))
2000                .map(|i| x.units[i].path.clone())
2001                .collect::<Vec<_>>(),
2002            ["C.a", "C", ""]
2003        );
2004    }
2005
2006    #[test]
2007    fn positions_are_one_based_byte_columns_like_anchors() {
2008        let x = code("p.js", "const é = 1; function f() {}\n").unwrap();
2009        let f = unit(&x, "f");
2010        assert_eq!((f.line, f.column), (1, 15), "é is two bytes");
2011        assert_eq!((f.end_line, f.end_column), (1, 30), "end is exclusive");
2012    }
2013
2014    #[test]
2015    fn an_unparsable_or_unknown_file_has_no_view() {
2016        assert!(code("a.js", "function (").is_none());
2017        assert!(code("data.json", "{}").is_none());
2018        assert!(code("README.md", "# hi").is_none());
2019    }
2020
2021    #[test]
2022    fn a_top_level_edit_changes_the_file_unit_and_only_it() {
2023        // Adding a top-level binding can shadow a name every function in the
2024        // file uses; the file unit is what carries that.
2025        let x = code("t.js", "export function f() { return g; }\n").unwrap();
2026        let y = code("t.js", "const g = 1;\nexport function f() { return g; }\n").unwrap();
2027        assert_ne!(x.units[0].digest, y.units[0].digest);
2028        assert_eq!(unit(&x, "f").digest, unit(&y, "f").digest);
2029        assert_eq!(x.structure, y.structure);
2030    }
2031}