diff --git a/.github/actions/setup-rust/action.yml b/.github/actions/setup-rust/action.yml index ba8cc849622bf..aa517bf051ba1 100644 --- a/.github/actions/setup-rust/action.yml +++ b/.github/actions/setup-rust/action.yml @@ -76,7 +76,7 @@ runs: - name: "Install cargo-sweep" uses: taiki-e/install-action@v2 with: - tool: cargo-sweep@0.6.2,cargo-groups + tool: cargo-sweep@0.6.2,cargo-groups@0.1.9 - name: "Run cargo-sweep" uses: ./.github/actions/cargo-sweep diff --git a/Cargo.lock b/Cargo.lock index 35c4f0a2e3e90..a3171527dc24d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -751,9 +751,9 @@ dependencies = [ [[package]] name = "biome_deserialize" -version = "0.5.7" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b4443260d505148169f5fb35634c2a60d8489882f8c9c3f1db8b7cf0cb57632" +checksum = "d6f619dc8ca0595ed8850d729ebc71722d4233aba68c5aec7d9993a53e59f3fe" dependencies = [ "biome_console", "biome_deserialize_macros", @@ -762,19 +762,18 @@ dependencies = [ "biome_json_syntax", "biome_rowan", "bitflags 2.5.0", - "indexmap 1.9.3", + "indexmap 2.2.6", "serde", "serde_json", - "tracing", ] [[package]] name = "biome_deserialize_macros" -version = "0.5.7" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fc1244cc5f0cc267bd26b601e9ccd6851c6a4d395bba07e27c2de641dc84479" +checksum = "07c12826fff87ac09f63bbacf8bdf5225dfdf890da04d426f758cbcacf068e3e" dependencies = [ - "convert_case 0.6.0", + "biome_string_case", "proc-macro-error", "proc-macro2", "quote", @@ -900,6 +899,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "biome_string_case" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28b4d0e08c2f13f1c9e0df4e7a8f9bfa03ef3803713d1bcd5110578cc5c67be" + [[package]] name = "biome_text_edit" version = "0.5.7" @@ -5915,9 +5920,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "unicode-ident", ] @@ -6941,9 +6946,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.115" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "indexmap 2.2.6", "itoa", @@ -10998,6 +11003,10 @@ name = "turbopath" version = "0.1.0" dependencies = [ "anyhow", + "biome_deserialize", + "biome_deserialize_macros", + "biome_diagnostics", + "biome_json_parser", "camino", "dunce", "fs-err", @@ -11008,6 +11017,7 @@ dependencies = [ "tempdir", "test-case", "thiserror", + "turborepo-unescape", "wax", ] @@ -11152,10 +11162,12 @@ name = "turborepo-errors" version = "0.1.0" dependencies = [ "biome_deserialize", + "biome_diagnostics", "miette 5.10.0", "serde", "serde_json", "test-case", + "thiserror", ] [[package]] @@ -11316,6 +11328,7 @@ dependencies = [ "turborepo-scm", "turborepo-telemetry", "turborepo-ui", + "turborepo-unescape", "turborepo-vercel-api", "turborepo-vercel-api-mock", "twox-hash", @@ -11384,9 +11397,15 @@ version = "0.1.0" dependencies = [ "anyhow", "async-once-cell", + "biome_deserialize", + "biome_deserialize_macros", + "biome_diagnostics", + "biome_json_parser", + "biome_json_syntax", "globwalk", "itertools 0.10.5", "lazy-regex", + "miette 5.10.0", "node-semver", "petgraph", "pretty_assertions", @@ -11402,8 +11421,10 @@ dependencies = [ "tokio-stream", "tracing", "turbopath", + "turborepo-errors", "turborepo-graph-utils", "turborepo-lockfiles", + "turborepo-unescape", "wax", "which", ] @@ -11483,6 +11504,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "turborepo-unescape" +version = "0.1.0" +dependencies = [ + "biome_deserialize", + "biome_diagnostics", + "serde", + "serde_json", +] + [[package]] name = "turborepo-vercel-api" version = "0.1.0" @@ -11533,7 +11564,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", - "rand 0.4.6", + "rand 0.8.5", "static_assertions", ] diff --git a/Cargo.toml b/Cargo.toml index 1087dc0ede7b8..58d5aea6ebbcf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -185,6 +185,7 @@ turborepo-lib = { path = "crates/turborepo-lib", default-features = false } turborepo-lockfiles = { path = "crates/turborepo-lockfiles" } turborepo-repository = { path = "crates/turborepo-repository" } turborepo-ui = { path = "crates/turborepo-ui" } +turborepo-unescape = { path = "crates/turborepo-unescape" } turborepo-scm = { path = "crates/turborepo-scm" } wax = { path = "crates/turborepo-wax" } turborepo-vercel-api = { path = "crates/turborepo-vercel-api" } @@ -214,11 +215,12 @@ async-trait = "0.1.64" atty = "0.2.14" axum = "0.6.2" axum-server = "0.4.4" -biome_console = "0.5.7" -biome_deserialize = "0.5.7" -biome_diagnostics = "0.5.7" -biome_json_parser = "0.5.7" -biome_json_syntax = "0.5.7" +biome_console = { version = "0.5.7" } +biome_deserialize = { version = "0.6.0", features = ["serde"] } +biome_deserialize_macros = { version = "0.6.0" } +biome_diagnostics = { version = "0.5.7" } +biome_json_parser = { version = "0.5.7" } +biome_json_syntax = { version = "0.5.7" } bytes = "1.1.0" camino = { version = "1.1.4", features = ["serde1"] } chrono = "0.4.23" diff --git a/crates/turborepo-errors/Cargo.toml b/crates/turborepo-errors/Cargo.toml index 3b2ce11bc2667..c5fc4394e3d47 100644 --- a/crates/turborepo-errors/Cargo.toml +++ b/crates/turborepo-errors/Cargo.toml @@ -1,4 +1,3 @@ - [package] name = "turborepo-errors" version = "0.1.0" @@ -9,8 +8,10 @@ license = "MIT" [dependencies] biome_deserialize = { workspace = true } +biome_diagnostics = { workspace = true } miette = { workspace = true } serde = { workspace = true, features = ["derive"] } +thiserror = { workspace = true } [dev-dependencies] serde_json = { workspace = true } diff --git a/crates/turborepo-errors/src/lib.rs b/crates/turborepo-errors/src/lib.rs index 6c9f3239ef9f6..45b3236fec0e2 100644 --- a/crates/turborepo-errors/src/lib.rs +++ b/crates/turborepo-errors/src/lib.rs @@ -1,14 +1,54 @@ use std::{ + fmt::Display, ops::{Deref, Range}, sync::Arc, }; use biome_deserialize::{Deserializable, DeserializableValue, DeserializationDiagnostic}; -use miette::{NamedSource, SourceSpan}; +use miette::{Diagnostic, NamedSource, SourceSpan}; use serde::{Deserialize, Serialize}; +use thiserror::Error; pub const TURBO_SITE: &str = "https://turbo.build"; +/// A little helper to convert from biome's syntax errors to miette. +#[derive(Debug, Error, Diagnostic)] +#[error("{message}")] +pub struct ParseDiagnostic { + message: String, + #[source_code] + source_code: String, + #[label] + label: Option, +} + +struct BiomeMessage<'a>(&'a biome_diagnostics::Error); + +impl Display for BiomeMessage<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.description(f) + } +} + +impl From for ParseDiagnostic { + fn from(diagnostic: biome_diagnostics::Error) -> Self { + let location = diagnostic.location(); + let message = BiomeMessage(&diagnostic).to_string(); + Self { + message, + source_code: location + .source_code + .map(|s| s.text.to_string()) + .unwrap_or_default(), + label: location.span.map(|span| { + let start: usize = span.start().into(); + let len: usize = span.len().into(); + (start, len).into() + }), + } + } +} + #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, Eq)] #[serde(transparent)] pub struct Spanned { diff --git a/crates/turborepo-lib/Cargo.toml b/crates/turborepo-lib/Cargo.toml index 1c24655c424cd..bab0762f04738 100644 --- a/crates/turborepo-lib/Cargo.toml +++ b/crates/turborepo-lib/Cargo.toml @@ -36,7 +36,7 @@ workspace = true atty = { workspace = true } axum = { workspace = true } biome_deserialize = { workspace = true } -biome_deserialize_macros = "0.5.7" +biome_deserialize_macros = { workspace = true } biome_diagnostics = { workspace = true } biome_json_parser = { workspace = true } biome_json_syntax = { workspace = true } @@ -128,6 +128,7 @@ turborepo-repository = { path = "../turborepo-repository" } turborepo-scm = { workspace = true } turborepo-telemetry = { path = "../turborepo-telemetry" } turborepo-ui = { workspace = true } +turborepo-unescape = { workspace = true } turborepo-vercel-api = { path = "../turborepo-vercel-api" } twox-hash = "1.6.3" uds_windows = "1.0.2" diff --git a/crates/turborepo-lib/src/lib.rs b/crates/turborepo-lib/src/lib.rs index c853bf85a79af..d1026703bc539 100644 --- a/crates/turborepo-lib/src/lib.rs +++ b/crates/turborepo-lib/src/lib.rs @@ -37,7 +37,6 @@ mod task_graph; mod task_hash; mod tracing; mod turbo_json; -mod unescape; pub use crate::{ child::spawn_child, diff --git a/crates/turborepo-lib/src/run/task_access.rs b/crates/turborepo-lib/src/run/task_access.rs index a790c5f65a8b1..c8056ece1ce43 100644 --- a/crates/turborepo-lib/src/run/task_access.rs +++ b/crates/turborepo-lib/src/run/task_access.rs @@ -10,11 +10,10 @@ use tracing::{debug, error, warn}; use turbopath::{AbsoluteSystemPathBuf, PathRelation}; use turborepo_cache::AsyncCache; use turborepo_scm::SCM; +use turborepo_unescape::UnescapedString; use super::ConfigCache; -use crate::{ - config::RawTurboJson, gitignore::ensure_turbo_is_gitignored, unescape::UnescapedString, -}; +use crate::{config::RawTurboJson, gitignore::ensure_turbo_is_gitignored}; // Environment variable key that will be used to enable, and set the expected // trace location diff --git a/crates/turborepo-lib/src/turbo_json/mod.rs b/crates/turborepo-lib/src/turbo_json/mod.rs index 18025cd6117f6..2519d65b753a6 100644 --- a/crates/turborepo-lib/src/turbo_json/mod.rs +++ b/crates/turborepo-lib/src/turbo_json/mod.rs @@ -13,6 +13,7 @@ use tracing::debug; use turbopath::{AbsoluteSystemPath, AnchoredSystemPath}; use turborepo_errors::Spanned; use turborepo_repository::{package_graph::ROOT_PKG_NAME, package_json::PackageJson}; +use turborepo_unescape::UnescapedString; use crate::{ cli::OutputLogsMode, @@ -22,7 +23,6 @@ use crate::{ task_id::{TaskId, TaskName}, }, task_graph::{TaskDefinition, TaskOutputs}, - unescape::UnescapedString, }; pub mod parser; @@ -735,6 +735,7 @@ mod tests { use test_case::test_case; use turbopath::{AbsoluteSystemPath, AnchoredSystemPath}; use turborepo_repository::package_json::PackageJson; + use turborepo_unescape::UnescapedString; use super::{Pipeline, RawTurboJson, Spanned, UI}; use crate::{ @@ -742,7 +743,6 @@ mod tests { run::task_id::TaskName, task_graph::{TaskDefinition, TaskOutputs}, turbo_json::{RawTaskDefinition, TurboJson}, - unescape::UnescapedString, }; #[test_case(r"{}", TurboJson::default() ; "empty")] diff --git a/crates/turborepo-lib/src/turbo_json/parser.rs b/crates/turborepo-lib/src/turbo_json/parser.rs index 47e16702a537c..5c8714a552bda 100644 --- a/crates/turborepo-lib/src/turbo_json/parser.rs +++ b/crates/turborepo-lib/src/turbo_json/parser.rs @@ -1,9 +1,4 @@ -use std::{ - backtrace, - collections::BTreeMap, - fmt::{Debug, Display}, - sync::Arc, -}; +use std::{backtrace, collections::BTreeMap, fmt::Debug, sync::Arc}; use biome_deserialize::{ json::deserialize_from_json_str, Deserializable, DeserializableValue, @@ -13,16 +8,16 @@ use biome_diagnostics::DiagnosticExt; use biome_json_parser::JsonParserOptions; use biome_json_syntax::TextRange; use convert_case::{Case, Casing}; -use miette::{Diagnostic, SourceSpan}; +use miette::Diagnostic; use struct_iterable::Iterable; use thiserror::Error; use turbopath::AnchoredSystemPath; -use turborepo_errors::WithMetadata; +use turborepo_errors::{ParseDiagnostic, WithMetadata}; +use turborepo_unescape::UnescapedString; use crate::{ run::task_id::TaskName, turbo_json::{Pipeline, RawTaskDefinition, RawTurboJson, Spanned}, - unescape::UnescapedString, }; #[derive(Debug, Error, Diagnostic)] @@ -35,44 +30,6 @@ pub struct Error { backtrace: backtrace::Backtrace, } -struct BiomeMessage<'a>(&'a biome_diagnostics::Error); - -impl Display for BiomeMessage<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.description(f) - } -} - -impl From for ParseDiagnostic { - fn from(diagnostic: biome_diagnostics::Error) -> Self { - let location = diagnostic.location(); - let message = BiomeMessage(&diagnostic).to_string(); - Self { - message, - source_code: location - .source_code - .map(|s| s.text.to_string()) - .unwrap_or_default(), - label: location.span.map(|span| { - let start: usize = span.start().into(); - let len: usize = span.len().into(); - (start, len).into() - }), - } - } -} - -#[derive(Debug, Error, Diagnostic)] -#[error("{message}")] -#[diagnostic(code(turbo_json_parse_error))] -struct ParseDiagnostic { - message: String, - #[source_code] - source_code: String, - #[label] - label: Option, -} - fn create_unknown_key_diagnostic_from_struct( struct_iterable: &T, unknown_key: &str, diff --git a/crates/turborepo-lsp/src/lib.rs b/crates/turborepo-lsp/src/lib.rs index 7ba126002a34a..9e72ceb4bc157 100644 --- a/crates/turborepo-lsp/src/lib.rs +++ b/crates/turborepo-lsp/src/lib.rs @@ -13,7 +13,6 @@ use std::{ borrow::Cow, collections::{HashMap, HashSet}, iter, - str::FromStr, sync::{Arc, Mutex}, }; @@ -288,7 +287,7 @@ impl LanguageServer for Backend { // so we just skip it and do a best effort Err(_) => continue, }; - let package_json = match PackageJson::from_str(&data) { + let package_json = match PackageJson::load_from_str(&data, wd.package_json.as_str()) { Ok(package_json) => package_json, // if we can't parse a package.json, then we can't set up references to it // so we just skip it and do a best effort diff --git a/crates/turborepo-paths/Cargo.toml b/crates/turborepo-paths/Cargo.toml index 73699b825139d..9d709a5085246 100644 --- a/crates/turborepo-paths/Cargo.toml +++ b/crates/turborepo-paths/Cargo.toml @@ -9,7 +9,22 @@ edition = "2021" [lints] workspace = true +[features] +biome = [ + "dep:biome_deserialize", + "dep:biome_deserialize_macros", + "dep:biome_diagnostics", + "dep:biome_json_parser", + "dep:turborepo-unescape", +] + [dependencies] +biome_deserialize = { workspace = true, optional = true } +biome_deserialize_macros = { workspace = true, optional = true } +biome_diagnostics = { workspace = true, optional = true } +biome_json_parser = { workspace = true, optional = true } +turborepo-unescape = { workspace = true, optional = true } + camino = { workspace = true } dunce = { workspace = true } fs-err = "2.9.0" diff --git a/crates/turborepo-paths/src/relative_unix_path_buf.rs b/crates/turborepo-paths/src/relative_unix_path_buf.rs index 15761f8818898..51657bf5e77ce 100644 --- a/crates/turborepo-paths/src/relative_unix_path_buf.rs +++ b/crates/turborepo-paths/src/relative_unix_path_buf.rs @@ -14,6 +14,33 @@ use crate::{PathError, RelativeUnixPath}; #[serde(try_from = "String", into = "String")] pub struct RelativeUnixPathBuf(pub(crate) String); +#[cfg(feature = "biome")] +mod biome { + use biome_deserialize::{Deserializable, DeserializableValue, DeserializationDiagnostic}; + use turborepo_unescape::UnescapedString; + + use crate::RelativeUnixPathBuf; + + impl Deserializable for RelativeUnixPathBuf { + fn deserialize( + value: &impl DeserializableValue, + name: &str, + diagnostics: &mut Vec, + ) -> Option { + let path: String = UnescapedString::deserialize(value, name, diagnostics)?.into(); + match Self::new(path) { + Ok(path) => Some(path), + Err(e) => { + diagnostics.push( + DeserializationDiagnostic::new(e.to_string()).with_range(value.range()), + ); + None + } + } + } + } +} + impl Display for RelativeUnixPathBuf { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { Display::fmt(&self.0, f) diff --git a/crates/turborepo-repository/Cargo.toml b/crates/turborepo-repository/Cargo.toml index 1f16fd54b72c5..c2f128f3f27c8 100644 --- a/crates/turborepo-repository/Cargo.toml +++ b/crates/turborepo-repository/Cargo.toml @@ -10,9 +10,17 @@ workspace = true [dependencies] anyhow = { workspace = true } async-once-cell = "0.5.3" + +biome_deserialize = { workspace = true } +biome_deserialize_macros = { workspace = true } +biome_diagnostics = { workspace = true } +biome_json_parser = { workspace = true } +biome_json_syntax = { workspace = true } + globwalk = { version = "0.1.0", path = "../turborepo-globwalk" } itertools = { workspace = true } lazy-regex = "2.5.0" +miette = { workspace = true } node-semver = "2.1.0" petgraph = { workspace = true } regex = { workspace = true } @@ -24,9 +32,11 @@ thiserror = "1.0.38" tokio-stream = "0.1.14" tokio.workspace = true tracing.workspace = true -turbopath = { workspace = true } +turbopath = { workspace = true, features = ["biome"] } +turborepo-errors = { workspace = true } turborepo-graph-utils = { path = "../turborepo-graph-utils" } turborepo-lockfiles = { workspace = true } +turborepo-unescape = { workspace = true } wax = { workspace = true } which = { workspace = true } diff --git a/crates/turborepo-repository/src/package_json.rs b/crates/turborepo-repository/src/package_json.rs index b1ba183177bc2..d58cea285b1c5 100644 --- a/crates/turborepo-repository/src/package_json.rs +++ b/crates/turborepo-repository/src/package_json.rs @@ -1,14 +1,17 @@ -use std::{ - collections::{BTreeMap, HashMap}, - str::FromStr, -}; +use std::collections::{BTreeMap, HashMap}; use anyhow::Result; -use serde::{Deserialize, Serialize}; -use serde_json::Value; +use biome_deserialize::{json::deserialize_from_json_str, Text}; +use biome_deserialize_macros::Deserializable; +use biome_diagnostics::DiagnosticExt; +use biome_json_parser::JsonParserOptions; +use miette::Diagnostic; +use serde::Serialize; use turbopath::{AbsoluteSystemPath, RelativeUnixPathBuf}; +use turborepo_errors::ParseDiagnostic; +use turborepo_unescape::UnescapedString; -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)] #[serde(rename_all = "camelCase")] pub struct PackageJson { #[serde(skip_serializing_if = "Option::is_none")] @@ -33,37 +36,134 @@ pub struct PackageJson { pub pnpm: Option, // Unstructured fields kept for round trip capabilities #[serde(flatten)] - pub other: BTreeMap, + pub other: BTreeMap, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)] #[serde(rename_all = "camelCase")] pub struct PnpmConfig { #[serde(skip_serializing_if = "Option::is_none")] pub patched_dependencies: Option>, // Unstructured config options kept for round trip capabilities #[serde(flatten)] - pub other: BTreeMap, + pub other: BTreeMap, } -#[derive(Debug, thiserror::Error)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Deserializable)] +pub struct RawPackageJson { + pub name: Option, + pub version: Option, + pub package_manager: Option, + pub dependencies: Option>, + pub dev_dependencies: Option>, + pub optional_dependencies: Option>, + pub peer_dependencies: Option>, + pub scripts: BTreeMap, + pub resolutions: Option>, + pub pnpm: Option, + // Unstructured fields kept for round trip capabilities + #[deserializable(rest)] + pub other: BTreeMap, +} + +#[derive(Debug, Default, Clone, PartialEq, Eq, Deserializable)] +pub struct RawPnpmConfig { + pub patched_dependencies: Option>, + // Unstructured config options kept for round trip capabilities + #[deserializable(rest)] + pub other: BTreeMap, +} + +#[derive(Debug, thiserror::Error, Diagnostic)] pub enum Error { #[error("unable to read package.json: {0}")] Io(#[from] std::io::Error), #[error("unable to parse package.json: {0}")] Json(#[from] serde_json::Error), + #[error("unable to parse package.json")] + #[diagnostic(code(package_json_parse_error))] + Parse(#[related] Vec), +} + +impl From for PackageJson { + fn from(raw: RawPackageJson) -> Self { + Self { + name: raw.name.map(|s| s.into()), + version: raw.version.map(|s| s.into()), + package_manager: raw.package_manager.map(|s| s.into()), + dependencies: raw + .dependencies + .map(|m| m.into_iter().map(|(k, v)| (k, v.into())).collect()), + dev_dependencies: raw + .dev_dependencies + .map(|m| m.into_iter().map(|(k, v)| (k, v.into())).collect()), + optional_dependencies: raw + .optional_dependencies + .map(|m| m.into_iter().map(|(k, v)| (k, v.into())).collect()), + peer_dependencies: raw + .peer_dependencies + .map(|m| m.into_iter().map(|(k, v)| (k, v.into())).collect()), + scripts: raw + .scripts + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect(), + resolutions: raw + .resolutions + .map(|m| m.into_iter().map(|(k, v)| (k, v.into())).collect()), + pnpm: raw.pnpm.map(|p| p.into()), + other: raw + .other + .into_iter() + .map(|(k, v)| (k.to_string(), v)) + .collect(), + } + } +} + +impl From for PnpmConfig { + fn from(raw: RawPnpmConfig) -> Self { + Self { + patched_dependencies: raw.patched_dependencies, + other: raw + .other + .into_iter() + .map(|(k, v)| (k.to_string(), v)) + .collect(), + } + } } impl PackageJson { pub fn load(path: &AbsoluteSystemPath) -> Result { tracing::debug!("loading package.json from {}", path); let contents = path.read_to_string()?; - Self::from_str(&contents) + Self::load_from_str(&contents, path.as_str()) + } + + pub fn load_from_str(contents: &str, path: &str) -> Result { + let (result, errors): (Option, _) = + deserialize_from_json_str(contents, JsonParserOptions::default(), path).consume(); + + match result { + Some(package_json) => Ok(package_json.into()), + None => Err(Error::Parse( + errors + .into_iter() + .map(|d| { + d.with_file_source_code(contents) + .with_file_path(path) + .into() + }) + .collect(), + )), + } } // Utility method for easy construction of package.json during testing pub fn from_value(value: serde_json::Value) -> Result { - let package_json: PackageJson = serde_json::from_value(value)?; + let contents = serde_json::to_string(&value)?; + let package_json: PackageJson = Self::load_from_str(&contents, "package.json")?; Ok(package_json) } @@ -97,14 +197,6 @@ impl PackageJson { } } -impl FromStr for PackageJson { - type Err = Error; - - fn from_str(s: &str) -> Result { - Ok(serde_json::from_str(s)?) - } -} - #[cfg(test)] mod test { use pretty_assertions::assert_eq; @@ -117,8 +209,16 @@ mod test { #[test_case(json!({"name": "foo", "resolutions": {"foo": "1.0.0"}}) ; "berry resolutions")] #[test_case(json!({"name": "foo", "pnpm": {"patchedDependencies": {"some-pkg": "./patchfile"}, "another-field": 1}}) ; "pnpm")] #[test_case(json!({"name": "foo", "pnpm": {"another-field": 1}}) ; "pnpm without patches")] - fn test_roundtrip(json: Value) { - let package_json: PackageJson = serde_json::from_value(json.clone()).unwrap(); + #[test_case(json!({"version": "1.2", "foo": "bar" }) ; "version")] + #[test_case(json!({"packageManager": "npm@9", "foo": "bar"}) ; "package manager")] + #[test_case(json!({"dependencies": { "turbo": "latest" }, "foo": "bar"}) ; "dependencies")] + #[test_case(json!({"devDependencies": { "turbo": "latest" }, "foo": "bar"}) ; "dev dependencies")] + #[test_case(json!({"optionalDependencies": { "turbo": "latest" }, "foo": "bar"}) ; "optional dependencies")] + #[test_case(json!({"peerDependencies": { "turbo": "latest" }, "foo": "bar"}) ; "peer dependencies")] + #[test_case(json!({"scripts": { "build": "turbo build" }, "foo": "bar"}) ; "scripts")] + #[test_case(json!({"resolutions": { "turbo": "latest" }, "foo": "bar"}) ; "resolutions")] + fn test_roundtrip(json: serde_json::Value) { + let package_json: PackageJson = PackageJson::from_value(json.clone()).unwrap(); let actual = serde_json::to_value(package_json).unwrap(); assert_eq!(actual, json); } diff --git a/crates/turborepo-repository/src/package_manager/pnpm.rs b/crates/turborepo-repository/src/package_manager/pnpm.rs index 50b4dd2de4d23..1bafa8e46b1f8 100644 --- a/crates/turborepo-repository/src/package_manager/pnpm.rs +++ b/crates/turborepo-repository/src/package_manager/pnpm.rs @@ -56,7 +56,7 @@ mod test { #[test] fn test_patch_pruning() { - let package_json: PackageJson = serde_json::from_value(json!({ + let package_json: PackageJson = PackageJson::from_value(json!({ "name": "pnpm-patches", "pnpm": { "patchedDependencies": { diff --git a/crates/turborepo-repository/src/package_manager/yarn.rs b/crates/turborepo-repository/src/package_manager/yarn.rs index 65511255edef0..25d5bb3dae46d 100644 --- a/crates/turborepo-repository/src/package_manager/yarn.rs +++ b/crates/turborepo-repository/src/package_manager/yarn.rs @@ -70,7 +70,7 @@ mod tests { #[test] fn test_patch_pruning() { - let package_json: PackageJson = serde_json::from_value(json!({ + let package_json: PackageJson = PackageJson::from_value(json!({ "name": "pnpm-patches", "resolutions": { "foo@1.0.0": "patch:foo@npm%3A1.0.0#./.yarn/patches/foo-npm-1.0.0-time.patch", diff --git a/crates/turborepo-unescape/Cargo.toml b/crates/turborepo-unescape/Cargo.toml new file mode 100644 index 0000000000000..8a745f4bdea10 --- /dev/null +++ b/crates/turborepo-unescape/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "turborepo-unescape" +version = "0.1.0" +edition = "2021" +license = "MPL-2.0" + +[dependencies] +biome_deserialize = { workspace = true } +biome_diagnostics = { workspace = true } +serde.workspace = true +serde_json.workspace = true + +[lints] +workspace = true diff --git a/crates/turborepo-lib/src/unescape.rs b/crates/turborepo-unescape/src/lib.rs similarity index 99% rename from crates/turborepo-lib/src/unescape.rs rename to crates/turborepo-unescape/src/lib.rs index db0f9f3edebf4..27e1b9a637fd6 100644 --- a/crates/turborepo-lib/src/unescape.rs +++ b/crates/turborepo-unescape/src/lib.rs @@ -27,7 +27,6 @@ impl Deref for UnescapedString { &self.0 } } - fn unescape_str(s: String) -> Result { let wrapped_s = format!("\"{}\"", s); diff --git a/turborepo-tests/integration/tests/bad-turbo-json.t b/turborepo-tests/integration/tests/bad-turbo-json.t index 68e75ec27806d..a4fa50b02cf24 100644 --- a/turborepo-tests/integration/tests/bad-turbo-json.t +++ b/turborepo-tests/integration/tests/bad-turbo-json.t @@ -76,27 +76,21 @@ Run build with syntax errors in turbo.json x failed to parse turbo json - Error: turbo_json_parse_error - - x Expected a property but instead found ','. + Error: x Expected a property but instead found ','. ,-[1:1] 1 | { 2 | "$schema": "https://turbo.build/schema.json",, : ^ 3 | "globalDependencies": ["foo.txt"], `---- - Error: turbo_json_parse_error - - x expected `,` but instead found `42` + Error: x expected `,` but instead found `42` ,-[11:1] 11 | "my-app#build": { 12 | "outputs": ["banana.txt", "apple.json"]42, : ^^ 13 | "inputs": [".env.local" `---- - Error: turbo_json_parse_error - - x expected `,` but instead found `}` + Error: x expected `,` but instead found `}` ,-[13:1] 13 | "inputs": [".env.local" 14 | },