fallow-core 2.40.2

Core analysis engine for the fallow TypeScript/JavaScript codebase analyzer
Documentation
#[allow(unused_imports, reason = "shared re-export for sibling test modules")]
pub(super) use std::path::{Path, PathBuf};

#[allow(unused_imports, reason = "shared re-export for sibling test modules")]
pub(super) use rustc_hash::{FxHashMap, FxHashSet};

#[allow(unused_imports, reason = "shared re-export for sibling test modules")]
pub(super) use fallow_config::{
    BoundaryConfig, FallowConfig, OutputFormat, PackageJson, ResolvedConfig, WorkspaceInfo,
};
#[allow(unused_imports, reason = "shared re-export for sibling test modules")]
pub(super) use fallow_types::discover::{DiscoveredFile, EntryPoint, EntryPointSource, FileId};
#[allow(unused_imports, reason = "shared re-export for sibling test modules")]
pub(super) use fallow_types::extract::{ImportInfo, ImportedName};

#[allow(unused_imports, reason = "shared re-export for sibling test modules")]
pub(super) use crate::graph::ModuleGraph;
#[allow(unused_imports, reason = "shared re-export for sibling test modules")]
pub(super) use crate::plugins::AggregatedPluginResult;
#[allow(unused_imports, reason = "shared re-export for sibling test modules")]
pub(super) use crate::resolve::{ResolveResult, ResolvedImport, ResolvedModule};
#[allow(unused_imports, reason = "shared re-export for sibling test modules")]
pub(super) use crate::results::*;
#[allow(unused_imports, reason = "shared re-export for sibling test modules")]
pub(super) use crate::suppress::{self, Suppression, SuppressionContext};

#[allow(unused_imports, reason = "shared re-export for sibling test modules")]
pub(super) use super::super::{
    DepCategoryConfig, LineOffsetsMap, SharedDepSets, collect_unused_for_category,
    find_import_location, find_test_only_dependencies, find_type_only_dependencies,
    find_unlisted_dependencies, find_unresolved_imports, find_unused_dependencies,
    is_package_listed_for_file, should_skip_dependency,
};

// ---- Integration test helpers ----

/// Build a minimal ResolvedConfig for testing.
pub(super) fn test_config(root: PathBuf) -> ResolvedConfig {
    FallowConfig::default().resolve(root, OutputFormat::Human, 1, true, true)
}

/// Build a PackageJson with specific dependency fields via JSON deserialization.
/// This avoids directly constructing `std::collections::HashMap` (clippy disallowed type).
pub(super) fn make_pkg(deps: &[&str], dev_deps: &[&str], optional_deps: &[&str]) -> PackageJson {
    let to_obj = |names: &[&str]| -> serde_json::Value {
        let map: serde_json::Map<String, serde_json::Value> = names
            .iter()
            .map(|n| {
                (
                    n.to_string(),
                    serde_json::Value::String("^1.0.0".to_string()),
                )
            })
            .collect();
        serde_json::Value::Object(map)
    };

    let mut obj = serde_json::Map::new();
    obj.insert(
        "name".to_string(),
        serde_json::Value::String("test-project".to_string()),
    );
    if !deps.is_empty() {
        obj.insert("dependencies".to_string(), to_obj(deps));
    }
    if !dev_deps.is_empty() {
        obj.insert("devDependencies".to_string(), to_obj(dev_deps));
    }
    if !optional_deps.is_empty() {
        obj.insert("optionalDependencies".to_string(), to_obj(optional_deps));
    }
    serde_json::from_value(serde_json::Value::Object(obj))
        .expect("test PackageJson should deserialize")
}

/// Build a minimal graph where a single entry file imports given npm packages.
#[expect(
    clippy::cast_possible_truncation,
    reason = "test span values are trivially small"
)]
pub(super) fn build_graph_with_npm_imports(
    npm_packages: &[(&str, bool)], // (package_name, is_type_only)
) -> (ModuleGraph, Vec<ResolvedModule>) {
    let files = vec![DiscoveredFile {
        id: FileId(0),
        path: PathBuf::from("/project/src/index.ts"),
        size_bytes: 100,
    }];

    let entry_points = vec![EntryPoint {
        path: PathBuf::from("/project/src/index.ts"),
        source: EntryPointSource::PackageJsonMain,
    }];

    let resolved_imports: Vec<ResolvedImport> = npm_packages
        .iter()
        .enumerate()
        .map(|(i, (name, is_type_only))| ResolvedImport {
            info: ImportInfo {
                source: name.to_string(),
                imported_name: ImportedName::Named("default".to_string()),
                local_name: format!("import_{i}"),
                is_type_only: *is_type_only,
                span: oxc_span::Span::new((i * 20) as u32, (i * 20 + 15) as u32),
                source_span: oxc_span::Span::default(),
            },
            target: ResolveResult::NpmPackage(name.to_string()),
        })
        .collect();

    let resolved_modules = vec![ResolvedModule {
        file_id: FileId(0),
        path: PathBuf::from("/project/src/index.ts"),
        exports: vec![],
        re_exports: vec![],
        resolved_imports,
        resolved_dynamic_imports: vec![],
        resolved_dynamic_patterns: vec![],
        member_accesses: vec![],
        whole_object_uses: vec![],
        has_cjs_exports: false,
        unused_import_bindings: FxHashSet::default(),
    }];

    let graph = ModuleGraph::build(&resolved_modules, &entry_points, &files);
    (graph, resolved_modules)
}

pub(super) type SkipDepSets = (
    FxHashSet<String>,
    FxHashSet<&'static str>,
    FxHashSet<&'static str>,
    FxHashSet<&'static str>,
    FxHashSet<&'static str>,
);

/// Helper: build empty sets for should_skip_dependency args.
pub(super) fn empty_sets() -> SkipDepSets {
    (
        FxHashSet::default(),
        FxHashSet::default(),
        FxHashSet::default(),
        FxHashSet::default(),
        FxHashSet::default(),
    )
}

pub(super) type SharedSets = (
    FxHashSet<&'static str>,
    FxHashSet<&'static str>,
    FxHashSet<&'static str>,
    FxHashSet<&'static str>,
    FxHashSet<&'static str>,
);

pub(super) fn empty_shared_sets() -> SharedSets {
    (
        FxHashSet::default(),
        FxHashSet::default(),
        FxHashSet::default(),
        FxHashSet::default(),
        FxHashSet::default(),
    )
}