skif 0.4.0

Embeddable facade for skif: discover a JS/TS workspace, lint it and plan fixes without IO side effects.
Documentation
//! The JS analysis provider, wired to the plugin table of `skif-js-plugins`.

use std::path::Path;
use std::sync::Arc;

use skif_config::Config;
use skif_core::FileSystem;
use skif_js::{DependencyExplanation, DependencyTrace, InputSource, JsAnalysis, JsProvider, PluginSource, UsageReport};
use skif_workspace::{PackageId, Workspace};

/// Builds the JS provider that reads through `fs` and takes its tool knowledge from the plugin table; a module cache
/// is added by the caller.
pub(crate) fn provider(config: &Config, fs: Arc<dyn FileSystem>) -> JsProvider {
    JsProvider::new(fs).with_input_source(Arc::new(
        PluginSource::new(config.js.plugins.clone())
            .with_workspaces(&config.js.workspaces)
            .with_root_tsconfig(config.js.tsconfig.as_deref())
            .with_overrides(&config.overrides),
    ))
}

/// Explains why the JavaScript analysis counts `file` (relative to the workspace root) as used, and with `export`
/// why that export of it is used or not.
///
/// The report names the entry rule when the file is an entry, lists every import of it with the importing line, and
/// shows the shortest chain of imports from an entry file; for an export it follows re-exports and ends in whether
/// another module uses it. It builds the whole analysis, so it costs as much as the JS part of a lint run.
#[must_use]
pub fn explain_usage(
    config: &Config,
    fs: &Arc<dyn FileSystem>,
    workspace: &Workspace,
    file: &Path,
    export: Option<&str>,
) -> UsageReport {
    let source: Arc<dyn InputSource> = Arc::new(
        PluginSource::new(config.js.plugins.clone())
            .with_workspaces(&config.js.workspaces)
            .with_root_tsconfig(config.js.tsconfig.as_deref())
            .with_overrides(&config.overrides),
    );
    JsAnalysis::build(workspace, config, fs, &[source], false).explain_usage(file, export)
}

/// Traces the dependency `name`: the manifests that list it, every import of it with the importing line, and the
/// packages that count it as used (through imports, scripts, tool configuration or its `@types` twin).
///
/// It builds the whole analysis, so it costs as much as the JS part of a lint run.
#[must_use]
pub fn trace_dependency(
    config: &Config,
    fs: &Arc<dyn FileSystem>,
    workspace: &Workspace,
    name: &str,
) -> DependencyTrace {
    let source: Arc<dyn InputSource> = Arc::new(
        PluginSource::new(config.js.plugins.clone())
            .with_workspaces(&config.js.workspaces)
            .with_root_tsconfig(config.js.tsconfig.as_deref())
            .with_overrides(&config.overrides),
    );
    JsAnalysis::build(workspace, config, fs, &[source], false).trace_dependency(name)
}

/// Explains why the dependency `name` is used or unused in every package that lists it or that something credits it
/// to, or with `package` in that package alone: the verdict `unused-dependencies` reaches, the files, scripts,
/// plugins and configs that credit it, and the imports of it that credit nothing.
///
/// It builds the whole analysis, so it costs as much as the JS part of a lint run.
#[must_use]
pub fn explain_dependency(
    config: &Config,
    fs: &Arc<dyn FileSystem>,
    workspace: &Workspace,
    name: &str,
    package: Option<PackageId>,
) -> DependencyExplanation {
    let source: Arc<dyn InputSource> = Arc::new(
        PluginSource::new(config.js.plugins.clone())
            .with_workspaces(&config.js.workspaces)
            .with_root_tsconfig(config.js.tsconfig.as_deref())
            .with_overrides(&config.overrides),
    );
    JsAnalysis::build(workspace, config, fs, &[source], false).explain_dependency(name, package)
}