Skip to main content

backhopper_core/
extract.rs

1// Copyright (C) 2026 Michael S. Klishin and Contributors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// See LICENSE-APACHE and LICENSE-MIT for details.
4
5//! Shared surface for the per-language source extractors.
6//!
7//! The token-driven Erlang scanner (`backhopper-erlang`) and the
8//! regex-driven Elixir scanner (`backhopper-elixir`) keep their own bodies,
9//! but speak this common vocabulary: the decode error, the extracted-source
10//! bag, and the visibility classifier.
11
12use std::path::PathBuf;
13
14use thiserror::Error;
15
16use crate::model::names::ModuleName;
17use crate::model::snapshot::{HrlFile, Module, Visibility};
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct ExtractedSource {
21    pub modules: Vec<Module>,
22    pub headers: Vec<HrlFile>,
23}
24
25#[derive(Debug, Error)]
26pub enum ExtractError {
27    #[error("invalid utf-8 in {path:?} at byte offset {offset}")]
28    InvalidUtf8 { path: PathBuf, offset: usize },
29}
30
31/// Resolves a module's [`Visibility`] from its name, the config's public and
32/// internal module lists, an out-of-band `hidden` hint (Erlang `%% @hidden`
33/// or `-doc(hidden)`, Elixir `@moduledoc false`), and whether its only
34/// exports are `-ifdef(TEST)`-guarded. An explicit internal listing wins; a
35/// public listing overrides the `hidden` hint.
36pub fn classify_visibility(
37    module: &ModuleName,
38    hidden: bool,
39    test_only: bool,
40    public_modules: &[String],
41    internal_modules: &[String],
42) -> Visibility {
43    if internal_modules.iter().any(|n| n == module.as_str()) {
44        return Visibility::Hidden;
45    }
46    if hidden && !public_modules.iter().any(|n| n == module.as_str()) {
47        return Visibility::Hidden;
48    }
49    if test_only {
50        return Visibility::TestOnly;
51    }
52    Visibility::Public
53}