tinymist_analysis/syntax/
import.rs

1//! Import resolution utilities.
2
3use crate::prelude::*;
4
5/// Resolves a file id by its import path.
6pub fn resolve_id_by_path(
7    world: &dyn World,
8    current: TypstFileId,
9    import_path: &str,
10) -> Option<TypstFileId> {
11    if import_path.starts_with('@') {
12        let spec = import_path.parse::<PackageSpec>().ok()?;
13        // Evaluates the manifest.
14        let manifest_id = TypstFileId::new(Some(spec.clone()), VirtualPath::new("typst.toml"));
15        let bytes = world.file(manifest_id).ok()?;
16        let string = std::str::from_utf8(&bytes).map_err(FileError::from).ok()?;
17        let manifest: PackageManifest = toml::from_str(string).ok()?;
18        manifest.validate(&spec).ok()?;
19
20        // Evaluates the entry point.
21        return Some(manifest_id.join(&manifest.package.entrypoint));
22    }
23
24    let path = Path::new(import_path);
25    let vpath = if path.is_relative() {
26        current.vpath().join(path)
27    } else {
28        VirtualPath::new(path)
29    };
30
31    Some(TypstFileId::new(current.package().cloned(), vpath))
32}
33
34/// Finds a source instance by its import node.
35pub fn find_source_by_expr(
36    world: &dyn World,
37    current: TypstFileId,
38    import_source: ast::Expr,
39) -> Option<Source> {
40    // todo: this could be valid: import("path.typ"), where v is parenthesized
41    match import_source {
42        ast::Expr::Str(s) => world
43            .source(resolve_id_by_path(world, current, s.get().as_str())?)
44            .ok(),
45        _ => None,
46    }
47}
48
49/// Casts a node to a single include expression.
50pub fn cast_include_expr<'a>(name: &str, node: ast::Expr<'a>) -> Option<ast::Expr<'a>> {
51    match node {
52        ast::Expr::Include(inc) => Some(inc.source()),
53        ast::Expr::Code(code) => {
54            let exprs = code.body();
55            if exprs.exprs().count() != 1 {
56                eprintln!("example function must have a single inclusion: {name}");
57                return None;
58            }
59            cast_include_expr(name, exprs.exprs().next().unwrap())
60        }
61        _ => {
62            eprintln!("example function must have a single inclusion: {name}");
63            None
64        }
65    }
66}