Skip to content

Commit

Permalink
new: TypeScript improvements. (#1190)
Browse files Browse the repository at this point in the history
* Add tests.

* Fix root handling.

* Use absolute paths.

* Add include types.

* Rewrite syncer.

* Start on blog post.

* More doc updates.

* Move code around.

* Bump

* Bump

* Improve paths.
  • Loading branch information
milesj authored Nov 19, 2023
1 parent 56dfc50 commit 31663e9
Show file tree
Hide file tree
Showing 23 changed files with 1,280 additions and 289 deletions.
2 changes: 2 additions & 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/core/utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ miette = { workspace = true }
once_cell = { workspace = true }
pathdiff = { workspace = true }
regex = { workspace = true }
relative-path = { workspace = true }
rustc-hash = { workspace = true }
semver = { workspace = true }
serde = { workspace = true }
Expand Down
16 changes: 16 additions & 0 deletions crates/core/utils/src/path.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use clean_path::Clean;
use miette::IntoDiagnostic;
use relative_path::PathExt;
use std::path::{Path, PathBuf};

pub use pathdiff::diff_paths as relative_from;
Expand Down Expand Up @@ -88,3 +90,17 @@ pub fn to_string<T: AsRef<Path>>(path: T) -> miette::Result<String> {
pub fn to_virtual_string<T: AsRef<Path>>(path: T) -> miette::Result<String> {
Ok(standardize_separators(to_string(path)?))
}

#[inline]
pub fn to_relative_virtual_string<F: AsRef<Path>, T: AsRef<Path>>(
from: F,
to: T,
) -> miette::Result<String> {
let value = from
.as_ref()
.relative_to(to.as_ref())
.into_diagnostic()?
.to_string();

Ok(if value.is_empty() { ".".into() } else { value })
}
8 changes: 2 additions & 6 deletions crates/deno/platform/src/actions/sync_project.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
use moon_common::Id;
use moon_config::{DenoConfig, TypeScriptConfig};
use moon_project::Project;
use moon_typescript_lang::tsconfig::CompilerOptionsPaths;
use rustc_hash::{FxHashMap, FxHashSet};
use std::collections::BTreeMap;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::sync::Arc;

// const LOG_TARGET: &str = "moon:deno-platform:sync-project";
Expand All @@ -17,16 +15,14 @@ pub async fn sync_project(
typescript_config: &Option<TypeScriptConfig>,
) -> miette::Result<bool> {
let mut mutated_project_files = false;
let tsconfig_project_refs: FxHashSet<String> = FxHashSet::default();
let tsconfig_paths: CompilerOptionsPaths = BTreeMap::new();
let tsconfig_project_refs: FxHashSet<PathBuf> = FxHashSet::default();

// Sync the project and root `tsconfig.json`
if let Some(typescript_config) = &typescript_config {
if moon_typescript_platform::sync_project(
project,
typescript_config,
workspace_root,
tsconfig_paths,
tsconfig_project_refs,
)? {
mutated_project_files = true;
Expand Down
47 changes: 3 additions & 44 deletions crates/node/platform/src/actions/sync_project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ use moon_config::{DependencyScope, NodeConfig, NodeVersionFormat, TypeScriptConf
use moon_logger::debug;
use moon_node_lang::{PackageJson, NPM};
use moon_project::Project;
use moon_typescript_lang::tsconfig::CompilerOptionsPaths;
use moon_utils::{path, semver};
use rustc_hash::{FxHashMap, FxHashSet};
use starbase_styles::color;
use std::collections::BTreeMap;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::sync::Arc;

const LOG_TARGET: &str = "moon:node-platform:sync-project";
Expand All @@ -27,8 +26,7 @@ pub async fn sync_project(
let mut package_prod_deps: BTreeMap<String, String> = BTreeMap::new();
let mut package_peer_deps: BTreeMap<String, String> = BTreeMap::new();
let mut package_dev_deps: BTreeMap<String, String> = BTreeMap::new();
let mut tsconfig_project_refs: FxHashSet<String> = FxHashSet::default();
let mut tsconfig_paths: CompilerOptionsPaths = BTreeMap::new();
let mut tsconfig_project_refs: FxHashSet<PathBuf> = FxHashSet::default();

for (dep_id, dep_cfg) in &project.dependencies {
let Some(dep_project) = dependencies.get(dep_id) else {
Expand Down Expand Up @@ -114,7 +112,7 @@ pub async fn sync_project(
.join(&typescript_config.project_config_file_name)
.exists()
{
tsconfig_project_refs.insert(path::to_virtual_string(&dep_relative_path)?);
tsconfig_project_refs.insert(dep_project.root.clone());

debug!(
target: LOG_TARGET,
Expand All @@ -124,44 +122,6 @@ pub async fn sync_project(
color::file(&typescript_config.project_config_file_name)
);
}

// Map the depended on reference as a `paths` alias using
// the dep's `package.json` name.
if is_project_typescript_enabled && is_dep_typescript_enabled {
if let Some(dep_package_json) = PackageJson::read(&dep_project.root)? {
if let Some(dep_package_name) = &dep_package_json.name {
for index in ["src/index.ts", "src/index.tsx", "index.ts", "index.tsx"] {
if dep_project.root.join(index).exists() {
tsconfig_paths.insert(
dep_package_name.clone(),
vec![path::to_virtual_string(dep_relative_path.join(index))?],
);

tsconfig_paths.insert(
format!("{dep_package_name}/*"),
vec![path::to_virtual_string(dep_relative_path.join(
if index.starts_with("src") {
"src/*"
} else {
"*"
},
))?],
);

debug!(
target: LOG_TARGET,
"Syncing {} as a import path alias to {}'s {}",
color::id(&dep_project.id),
color::id(&project.id),
color::file(&typescript_config.project_config_file_name)
);

break;
}
}
}
}
}
}
}

Expand Down Expand Up @@ -205,7 +165,6 @@ pub async fn sync_project(
project,
typescript_config,
workspace_root,
tsconfig_paths,
tsconfig_project_refs,
)? {
mutated_project_files = true;
Expand Down
59 changes: 47 additions & 12 deletions crates/typescript/lang/src/tsconfig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use cached::proc_macro::cached;
use miette::IntoDiagnostic;
use moon_lang::config_cache;
use moon_utils::path::standardize_separators;
use moon_utils::path::to_relative_virtual_string;
use serde::{Deserialize, Deserializer, Serialize};
use starbase_utils::json::{self, read_file as read_json, JsonMap, JsonValue};
use std::collections::BTreeMap;
Expand Down Expand Up @@ -81,34 +81,58 @@ impl TsConfigJson {
Ok(cfg)
}

pub fn add_include<T: AsRef<str>>(&mut self, pattern: T) -> bool {
let pattern = pattern.as_ref();
let mut include = match &self.include {
Some(refs) => refs.clone(),
None => Vec::<String>::new(),
};

if include.iter().any(|p| p == pattern) {
return false;
}

include.push(pattern.to_owned());
include.sort();

self.dirty.push("include".into());
self.include = Some(include);

true
}

pub fn add_include_path<T: AsRef<Path>>(&mut self, path: T) -> miette::Result<bool> {
let path = to_relative_virtual_string(path.as_ref(), self.path.parent().unwrap())?;

Ok(self.add_include(path.as_str()))
}

/// Add a project reference to the `references` field with the defined
/// path and tsconfig file name, and sort the list based on path.
/// Return true if the new value is different from the old value.
pub fn add_project_ref<T: AsRef<str>, C: AsRef<str>>(
pub fn add_project_ref<T: AsRef<Path>, C: AsRef<str>>(
&mut self,
base_path: T,
tsconfig_name: C,
) -> bool {
let base_path = base_path.as_ref();
) -> miette::Result<bool> {
let mut base_path = base_path.as_ref().to_path_buf();
let tsconfig_name = tsconfig_name.as_ref();
let mut path = standardize_separators(base_path);

// File name is optional when using standard naming
if tsconfig_name != "tsconfig.json" {
path = format!("{path}/{tsconfig_name}")
base_path = base_path.join(tsconfig_name);
};

let path = to_relative_virtual_string(base_path, self.path.parent().unwrap())?;

let mut references = match &self.references {
Some(refs) => refs.clone(),
None => Vec::<Reference>::new(),
};

// Check if the reference already exists
if references
.iter()
.any(|r| r.path == path || r.path == base_path)
{
return false;
if references.iter().any(|r| r.path == path) {
return Ok(false);
}

// Add and sort the references
Expand All @@ -122,7 +146,7 @@ impl TsConfigJson {
self.dirty.push("references".into());
self.references = Some(references);

true
Ok(true)
}

pub fn update_compiler_options<F>(&mut self, updater: F) -> bool
Expand Down Expand Up @@ -795,6 +819,13 @@ fn write_preserved_json(path: &Path, tsconfig: &TsConfigJson) -> miette::Result<
// otherwise it's a ton of overhead and maintenance!
for field in &tsconfig.dirty {
match field.as_ref() {
"include" => {
if let Some(include) = &tsconfig.include {
data[field] = JsonValue::from_iter(include.to_owned());
} else if let Some(root) = data.as_object_mut() {
root.remove(field);
}
}
"references" => {
if let Some(references) = &tsconfig.references {
let mut list = vec![];
Expand All @@ -811,6 +842,8 @@ fn write_preserved_json(path: &Path, tsconfig: &TsConfigJson) -> miette::Result<
}

data[field] = JsonValue::Array(list);
} else if let Some(root) = data.as_object_mut() {
root.remove(field);
}
}
"compilerOptions" => {
Expand All @@ -828,6 +861,8 @@ fn write_preserved_json(path: &Path, tsconfig: &TsConfigJson) -> miette::Result<
if let Some(paths) = &options.paths {
data[field]["paths"] = JsonValue::from_iter(paths.to_owned());
}
} else if let Some(root) = data.as_object_mut() {
root.remove(field);
}
}
_ => {}
Expand Down
39 changes: 31 additions & 8 deletions crates/typescript/lang/tests/tsconfig_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use moon_typescript_lang::tsconfig::*;
use moon_utils::string_vec;
use starbase_utils::json::{self, JsonValue};
use std::collections::BTreeMap;
use std::path::PathBuf;

#[test]
fn preserves_when_saving() {
Expand Down Expand Up @@ -225,11 +226,16 @@ mod add_project_ref {

#[test]
fn adds_if_not_set() {
let mut tsc = TsConfigJson::default();
let mut tsc = TsConfigJson {
path: PathBuf::from("/base/tsconfig.json"),
..TsConfigJson::default()
};

assert_eq!(tsc.references, None);

assert!(tsc.add_project_ref("../sibling", "tsconfig.json"));
assert!(tsc
.add_project_ref(PathBuf::from("/sibling"), "tsconfig.json")
.unwrap());

assert_eq!(
tsc.references.unwrap(),
Expand All @@ -247,10 +253,13 @@ mod add_project_ref {
path: "../sibling".to_owned(),
prepend: None,
}]),
path: PathBuf::from("/base/tsconfig.json"),
..TsConfigJson::default()
};

assert!(!tsc.add_project_ref("../sibling", "tsconfig.json"));
assert!(!tsc
.add_project_ref(PathBuf::from("/sibling"), "tsconfig.json")
.unwrap());

assert_eq!(
tsc.references.unwrap(),
Expand All @@ -263,11 +272,16 @@ mod add_project_ref {

#[test]
fn includes_custom_config_name() {
let mut tsc = TsConfigJson::default();
let mut tsc = TsConfigJson {
path: PathBuf::from("/base/tsconfig.json"),
..TsConfigJson::default()
};

assert_eq!(tsc.references, None);

assert!(tsc.add_project_ref("../sibling", "tsconfig.ref.json"));
assert!(tsc
.add_project_ref(PathBuf::from("/sibling"), "tsconfig.ref.json")
.unwrap());

assert_eq!(
tsc.references.unwrap(),
Expand All @@ -278,13 +292,19 @@ mod add_project_ref {
);
}

#[cfg(windows)]
#[test]
fn forces_forward_slash() {
let mut tsc = TsConfigJson::default();
let mut tsc = TsConfigJson {
path: PathBuf::from("C:\\base\\dir\\tsconfig.json"),
..TsConfigJson::default()
};

assert_eq!(tsc.references, None);

assert!(tsc.add_project_ref("..\\sibling", "tsconfig.json"));
assert!(tsc
.add_project_ref(PathBuf::from("C:\\base\\sibling"), "tsconfig.json")
.unwrap());

assert_eq!(
tsc.references.unwrap(),
Expand All @@ -302,10 +322,13 @@ mod add_project_ref {
path: "../sister".to_owned(),
prepend: None,
}]),
path: PathBuf::from("/base/tsconfig.json"),
..TsConfigJson::default()
};

assert!(tsc.add_project_ref("../brother", "tsconfig.json"));
assert!(tsc
.add_project_ref(PathBuf::from("/brother"), "tsconfig.json")
.unwrap());

assert_eq!(
tsc.references.unwrap(),
Expand Down
1 change: 1 addition & 0 deletions crates/typescript/platform/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ publish = false
moon_config = { path = "../../../nextgen/config" }
moon_hash = { path = "../../../nextgen/hash" }
moon_logger = { path = "../../core/logger" }
moon_node_lang = { path = "../../node/lang" }
moon_project = { path = "../../../nextgen/project" }
moon_utils = { path = "../../core/utils" }
moon_typescript_lang = { path = "../lang" }
Expand Down
Loading

0 comments on commit 31663e9

Please sign in to comment.