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}