turbopack_core/
package_json.rs

1use std::ops::Deref;
2
3use anyhow::Result;
4use serde_json::Value as JsonValue;
5use turbo_rcstr::{RcStr, rcstr};
6use turbo_tasks::{
7    NonLocalValue, ReadRef, ResolvedVc, Vc, debug::ValueDebugFormat, trace::TraceRawVcs,
8};
9use turbo_tasks_fs::{FileJsonContent, FileSystemPath};
10
11use super::issue::Issue;
12use crate::{
13    asset::Asset,
14    issue::{
15        IssueExt, IssueSource, IssueStage, OptionIssueSource, OptionStyledString, StyledString,
16    },
17    source::Source,
18    source_pos::SourcePos,
19};
20
21/// PackageJson wraps the parsed JSON content of a `package.json` file. The
22/// wrapper is necessary so that we can reference the [FileJsonContent]'s inner
23/// [serde_json::Value] without cloning it.
24#[derive(PartialEq, Eq, ValueDebugFormat, TraceRawVcs, NonLocalValue)]
25pub struct PackageJson(ReadRef<FileJsonContent>);
26
27impl Deref for PackageJson {
28    type Target = JsonValue;
29    fn deref(&self) -> &Self::Target {
30        match &*self.0 {
31            FileJsonContent::Content(json) => json,
32            _ => unreachable!("PackageJson is guaranteed to hold Content"),
33        }
34    }
35}
36
37#[turbo_tasks::value(transparent, serialization = "none")]
38pub struct OptionPackageJson(Option<PackageJson>);
39
40/// Reads a package.json file (if it exists). If the file is unparsable, it
41/// emits a useful [Issue] pointing to the invalid location.
42#[turbo_tasks::function]
43pub async fn read_package_json(path: ResolvedVc<Box<dyn Source>>) -> Result<Vc<OptionPackageJson>> {
44    let read = path.content().parse_json().await?;
45    match &*read {
46        FileJsonContent::Content(_) => Ok(OptionPackageJson(Some(PackageJson(read))).cell()),
47        FileJsonContent::NotFound => Ok(OptionPackageJson(None).cell()),
48        FileJsonContent::Unparsable(e) => {
49            let error_message = RcStr::from(format!(
50                "package.json is not parseable: invalid JSON: {}",
51                e.message
52            ));
53
54            let source = match (e.start_location, e.end_location) {
55                (None, None) => IssueSource::from_source_only(path),
56                (Some((line, column)), None) | (None, Some((line, column))) => {
57                    IssueSource::from_line_col(
58                        path,
59                        SourcePos { line, column },
60                        SourcePos { line, column },
61                    )
62                }
63                (Some((start_line, start_column)), Some((end_line, end_column))) => {
64                    IssueSource::from_line_col(
65                        path,
66                        SourcePos {
67                            line: start_line,
68                            column: start_column,
69                        },
70                        SourcePos {
71                            line: end_line,
72                            column: end_column,
73                        },
74                    )
75                }
76            };
77            PackageJsonIssue {
78                error_message,
79                source,
80            }
81            .resolved_cell()
82            .emit();
83            Ok(OptionPackageJson(None).cell())
84        }
85    }
86}
87
88/// Reusable Issue struct representing any problem with a `package.json`
89#[turbo_tasks::value(shared)]
90pub struct PackageJsonIssue {
91    pub error_message: RcStr,
92    pub source: IssueSource,
93}
94
95#[turbo_tasks::value_impl]
96impl Issue for PackageJsonIssue {
97    #[turbo_tasks::function]
98    fn title(&self) -> Vc<StyledString> {
99        StyledString::Text(rcstr!("Error parsing package.json file")).cell()
100    }
101
102    #[turbo_tasks::function]
103    fn stage(&self) -> Vc<IssueStage> {
104        IssueStage::Parse.cell()
105    }
106
107    #[turbo_tasks::function]
108    fn file_path(&self) -> Vc<FileSystemPath> {
109        self.source.file_path()
110    }
111
112    #[turbo_tasks::function]
113    fn description(&self) -> Vc<OptionStyledString> {
114        Vc::cell(Some(
115            StyledString::Text(self.error_message.clone()).resolved_cell(),
116        ))
117    }
118
119    #[turbo_tasks::function]
120    fn source(&self) -> Vc<OptionIssueSource> {
121        Vc::cell(Some(self.source))
122    }
123}