next.js/turbopack/crates/turbopack-core/src/package_json.rs
package_json.rs96 lines2.9 KB
use std::ops::Deref;

use anyhow::Result;
use async_trait::async_trait;
use serde_json::Value as JsonValue;
use turbo_rcstr::{RcStr, rcstr};
use turbo_tasks::{
    NonLocalValue, ReadRef, ResolvedVc, Vc, debug::ValueDebugFormat, trace::TraceRawVcs,
};
use turbo_tasks_fs::{FileJsonContent, FileSystemPath};

use super::issue::Issue;
use crate::{
    asset::Asset,
    issue::{IssueExt, IssueSource, IssueStage, StyledString},
    source::Source,
};

/// PackageJson wraps the parsed JSON content of a `package.json` file. The
/// wrapper is necessary so that we can reference the [FileJsonContent]'s inner
/// [serde_json::Value] without cloning it.
#[derive(PartialEq, Eq, ValueDebugFormat, TraceRawVcs, NonLocalValue)]
pub struct PackageJson(ReadRef<FileJsonContent>);

impl Deref for PackageJson {
    type Target = JsonValue;
    fn deref(&self) -> &Self::Target {
        match &*self.0 {
            FileJsonContent::Content(json) => json,
            _ => unreachable!("PackageJson is guaranteed to hold Content"),
        }
    }
}

#[turbo_tasks::value(transparent, serialization = "skip")]
pub struct OptionPackageJson(Option<PackageJson>);

/// Reads a package.json file (if it exists). If the file is unparsable, it
/// emits a useful [Issue] pointing to the invalid location.
#[turbo_tasks::function]
pub async fn read_package_json(path: ResolvedVc<Box<dyn Source>>) -> Result<Vc<OptionPackageJson>> {
    let read = path.content().parse_json().await?;
    match &*read {
        FileJsonContent::Content(_) => Ok(OptionPackageJson(Some(PackageJson(read))).cell()),
        FileJsonContent::NotFound => Ok(OptionPackageJson(None).cell()),
        FileJsonContent::Unparsable(e) => {
            let error_message = RcStr::from(format!(
                "package.json is not parseable: invalid JSON: {}",
                e.message
            ));

            let source = IssueSource::from_unparsable_json(path, e);
            PackageJsonIssue {
                error_message,
                source,
            }
            .resolved_cell()
            .emit();
            Ok(OptionPackageJson(None).cell())
        }
    }
}

/// Reusable Issue struct representing any problem with a `package.json`
#[turbo_tasks::value(shared)]
pub struct PackageJsonIssue {
    pub error_message: RcStr,
    pub source: IssueSource,
}

#[async_trait]
#[turbo_tasks::value_impl]
impl Issue for PackageJsonIssue {
    async fn title(&self) -> Result<StyledString> {
        Ok(StyledString::Text(rcstr!(
            "Error parsing package.json file"
        )))
    }

    fn stage(&self) -> IssueStage {
        IssueStage::Parse
    }

    async fn file_path(&self) -> Result<FileSystemPath> {
        self.source.file_path().owned().await
    }

    async fn description(&self) -> Result<Option<StyledString>> {
        Ok(Some(StyledString::Text(self.error_message.clone())))
    }

    fn source(&self) -> Option<IssueSource> {
        Some(self.source)
    }
}
Quest for Codev2.0.0
/
SIGN IN