Skip to content

Commit

Permalink
AST-based modification of turbo.json (#5509)
Browse files Browse the repository at this point in the history
When we run `link` and `unlink` we want those commands to create the
smallest set of changes possible in the output while also accounting for
edge cases like multiple definition.

This PR adds an AST-aware JSON-rewriter that modifies the file contents
rather than attempting to deserialize and reserialize the entire
document (which would otherwise be lossy unless deserialized to a
concrete syntax tree).

The only implementation of a concrete syntax tree for JSON in the Rust
ecosystem appears to be
https://github.com/rome/tools/tree/main/crates/rome_json_parser which
presently lexes `jsonc`, but does not link it into the rest of the
parser. There is an open, abandoned PR for that effort:
rome/tools#4382

After review, I decided that the additional complexity and size of
pulling in the entire Rome parsing toolchain was not going to be
worthwhile at this time for a task this small in scope.

I investigated `pest` as an alternative, but grammar correctness edge
cases for JSON are not great, and the whitespace and comment macros in
the `pest` grammar would have to be hand-implemented. I elected against
this for low confidence in a toy parser.

Eventually I chose to use `jsonc_parser` which doesn't actually create
all of the necessary tokens, but using conservative patterns on top of
the limited information I can ensure that it doesn't _fail_. However,
this greatly limits the formatting abilities that we can accomplish. I
decided this was an acceptable tradeoff for complexity, total effort,
dep size, scope of use (just `link` and `unlink` setting one property).

Additional output formatting improvements are out-of-scope for this PR.
This generates assuredly parseable content, but makes no guarantees as
to what the output format looks like.

---------

Co-authored-by: Nathan Hammond <Nathan Hammond>
  • Loading branch information
nathanhammond authored Aug 14, 2023
1 parent 1e68c27 commit a2b3fee
Show file tree
Hide file tree
Showing 6 changed files with 446 additions and 42 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/turborepo-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ humantime = "2.1.0"
indicatif = { workspace = true }
itertools = { workspace = true }
json_comments = "0.2.1"
jsonc-parser = { version = "0.21.0" }
lazy_static = { workspace = true }
libc = "0.2.140"
notify = "5.1"
Expand Down
30 changes: 9 additions & 21 deletions crates/turborepo-lib/src/commands/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use crate::{
cli::LinkTarget,
commands::CommandBase,
config::{RawTurboJSON, SpacesJson},
rewrite_json,
};

#[derive(Clone)]
Expand Down Expand Up @@ -443,29 +444,16 @@ fn add_turbo_to_gitignore(base: &CommandBase) -> Result<()> {

fn add_space_id_to_turbo_json(base: &CommandBase, space_id: &str) -> Result<()> {
let turbo_json_path = base.repo_root.join_component("turbo.json");
let turbo_json = fs::read_to_string(&turbo_json_path)?;
let space_id_json_value = format!("\"{}\"", space_id);

if !turbo_json_path.exists() {
return Err(anyhow!("turbo.json not found."));
}

let turbo_json_file = File::open(&turbo_json_path)?;
let mut turbo_json: RawTurboJSON = serde_json::from_reader(turbo_json_file)?;
match turbo_json.experimental_spaces {
Some(mut spaces_config) => {
spaces_config.id = Some(space_id.to_string());
turbo_json.experimental_spaces = Some(spaces_config);
}
None => {
turbo_json.experimental_spaces = Some(SpacesJson {
id: Some(space_id.to_string()),
other: None,
});
}
}
let output = rewrite_json::set_path(
&turbo_json,
&["experimentalSpaces", "id"],
&space_id_json_value,
)?;

// write turbo_json back to file
let config_file = File::create(&turbo_json_path)?;
serde_json::to_writer_pretty(&config_file, &turbo_json)?;
fs::write(turbo_json_path, output)?;

Ok(())
}
Expand Down
30 changes: 9 additions & 21 deletions crates/turborepo-lib/src/commands/unlink.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::{fs, fs::File};
use std::fs;

use anyhow::{Context, Result};
use turborepo_ui::GREY;

use crate::{cli::LinkTarget, commands::CommandBase, config::RawTurboJSON};
use crate::{cli::LinkTarget, commands::CommandBase, config::RawTurboJSON, rewrite_json};

enum UnlinkSpacesResult {
Unlinked,
Expand Down Expand Up @@ -55,25 +55,13 @@ pub fn unlink(base: &mut CommandBase, target: LinkTarget) -> Result<()> {

fn remove_spaces_from_turbo_json(base: &CommandBase) -> Result<UnlinkSpacesResult> {
let turbo_json_path = base.repo_root.join_component("turbo.json");
let turbo_json = fs::read_to_string(&turbo_json_path)?;

let turbo_json_contents =
fs::read_to_string(&turbo_json_path).context("unable to open turbo.json file")?;
let mut turbo_json: RawTurboJSON = serde_json::from_str(&turbo_json_contents)?;
let has_spaces_id = turbo_json
.experimental_spaces
.unwrap_or_default()
.id
.is_some();
// remove the spaces config
// TODO: in the future unlink should possible just remove the spaces id
turbo_json.experimental_spaces = None;

// write turbo_json back to file
let config_file = File::create(&turbo_json_path)?;
serde_json::to_writer_pretty(&config_file, &turbo_json)?;

match has_spaces_id {
true => Ok(UnlinkSpacesResult::Unlinked),
false => Ok(UnlinkSpacesResult::NoSpacesFound),
let output = rewrite_json::unset_path(&turbo_json, &["experimentalSpaces", "id"])?;
if let Some(output) = output {
fs::write(turbo_json_path, output)?;
Ok(UnlinkSpacesResult::Unlinked)
} else {
Ok(UnlinkSpacesResult::NoSpacesFound)
}
}
1 change: 1 addition & 0 deletions crates/turborepo-lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mod opts;
mod package_graph;
mod package_json;
mod package_manager;
mod rewrite_json;
mod run;
mod shim;
mod task_graph;
Expand Down
Loading

0 comments on commit a2b3fee

Please sign in to comment.