turbopack_core/
package_json.rs

1use std::{fmt::Write, ops::Deref};
2
3use anyhow::Result;
4use serde_json::Value as JsonValue;
5use turbo_rcstr::RcStr;
6use turbo_tasks::{
7    NonLocalValue, ReadRef, ResolvedVc, Vc, debug::ValueDebugFormat, trace::TraceRawVcs,
8};
9use turbo_tasks_fs::{FileContent, FileJsonContent, FileSystemPath};
10
11use super::issue::Issue;
12use crate::issue::{IssueExt, IssueStage, OptionStyledString, StyledString};
13
14/// PackageJson wraps the parsed JSON content of a `package.json` file. The
15/// wrapper is necessary so that we can reference the [FileJsonContent]'s inner
16/// [serde_json::Value] without cloning it.
17#[derive(PartialEq, Eq, ValueDebugFormat, TraceRawVcs, NonLocalValue)]
18pub struct PackageJson(ReadRef<FileJsonContent>);
19
20impl Deref for PackageJson {
21    type Target = JsonValue;
22    fn deref(&self) -> &Self::Target {
23        match &*self.0 {
24            FileJsonContent::Content(json) => json,
25            _ => unreachable!("PackageJson is guaranteed to hold Content"),
26        }
27    }
28}
29
30#[turbo_tasks::value(transparent, serialization = "none")]
31pub struct OptionPackageJson(Option<PackageJson>);
32
33/// Reads a package.json file (if it exists). If the file is unparseable, it
34/// emits a useful [Issue] pointing to the invalid location.
35#[turbo_tasks::function]
36pub async fn read_package_json(path: ResolvedVc<FileSystemPath>) -> Result<Vc<OptionPackageJson>> {
37    let read = path.read_json().await?;
38    match &*read {
39        FileJsonContent::Content(_) => Ok(OptionPackageJson(Some(PackageJson(read))).cell()),
40        FileJsonContent::NotFound => Ok(OptionPackageJson(None).cell()),
41        FileJsonContent::Unparseable(e) => {
42            let mut message = "package.json is not parseable: invalid JSON: ".to_string();
43            if let FileContent::Content(content) = &*path.read().await? {
44                let text = content.content().to_str()?;
45                e.write_with_content(&mut message, &text)?;
46            } else {
47                write!(message, "{e}")?;
48            }
49            PackageJsonIssue {
50                error_message: message.into(),
51                path,
52            }
53            .resolved_cell()
54            .emit();
55            Ok(OptionPackageJson(None).cell())
56        }
57    }
58}
59
60/// Reusable Issue struct representing any problem with a `package.json`
61#[turbo_tasks::value(shared)]
62pub struct PackageJsonIssue {
63    pub path: ResolvedVc<FileSystemPath>,
64    pub error_message: RcStr,
65}
66
67#[turbo_tasks::value_impl]
68impl Issue for PackageJsonIssue {
69    #[turbo_tasks::function]
70    fn title(&self) -> Vc<StyledString> {
71        StyledString::Text("Error parsing package.json file".into()).cell()
72    }
73
74    #[turbo_tasks::function]
75    fn stage(&self) -> Vc<IssueStage> {
76        IssueStage::Parse.cell()
77    }
78
79    #[turbo_tasks::function]
80    fn file_path(&self) -> Vc<FileSystemPath> {
81        *self.path
82    }
83
84    #[turbo_tasks::function]
85    fn description(&self) -> Vc<OptionStyledString> {
86        Vc::cell(Some(
87            StyledString::Text(self.error_message.clone()).resolved_cell(),
88        ))
89    }
90}