test the wasm side (#6726)

Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
Jess Frazelle
2025-05-06 20:04:34 -07:00
committed by GitHub
parent 17c326e654
commit e373d285fe
30 changed files with 594 additions and 330 deletions

View File

@ -22,6 +22,12 @@ extern "C" {
#[derive(Debug, Clone)]
pub type EngineCommandManager;
#[wasm_bindgen(constructor)]
pub fn new() -> EngineCommandManager;
#[wasm_bindgen(method, js_name = startFromWasm, catch)]
pub async fn start_from_wasm(this: &EngineCommandManager, token: &str) -> Result<JsValue, js_sys::Error>;
#[wasm_bindgen(method, js_name = fireModelingCommandFromWasm, catch)]
fn fire_modeling_cmd_from_wasm(
this: &EngineCommandManager,

View File

@ -676,7 +676,9 @@ extrude(profile001, length = 100)"#
std::fs::write(tmp_file, other_file.1).unwrap();
let ExecTestResults { program, exec_ctxt, .. } =
parse_execute_with_project_dir(code, Some(tmp_dir)).await.unwrap();
parse_execute_with_project_dir(code, Some(crate::TypedPath(tmp_dir)))
.await
.unwrap();
let mut new_program = crate::Program::parse_no_errs(code).unwrap();
new_program.compute_digest();
@ -755,7 +757,9 @@ extrude(profile001, length = 100)
std::fs::write(&tmp_file, other_file.1).unwrap();
let ExecTestResults { program, exec_ctxt, .. } =
parse_execute_with_project_dir(code, Some(tmp_dir)).await.unwrap();
parse_execute_with_project_dir(code, Some(crate::TypedPath(tmp_dir)))
.await
.unwrap();
// Change the other file.
std::fs::write(tmp_file, other_file2.1).unwrap();

View File

@ -2643,7 +2643,7 @@ d = b + c
)),
fs: Arc::new(crate::fs::FileManager::new()),
settings: ExecutorSettings {
project_directory: Some(tmpdir.path().into()),
project_directory: Some(crate::TypedPath(tmpdir.path().into())),
..Default::default()
},
stdlib: Arc::new(crate::std::StdLib::new()),

View File

@ -1,4 +1,4 @@
use std::{ffi::OsStr, path::Path, str::FromStr};
use std::str::FromStr;
use anyhow::Result;
use kcmc::{
@ -15,7 +15,7 @@ use uuid::Uuid;
use crate::{
errors::{KclError, KclErrorDetails},
execution::{annotations, types::UnitLen, ExecState, ExecutorContext, ImportedGeometry},
execution::{annotations, typed_path::TypedPath, types::UnitLen, ExecState, ExecutorContext, ImportedGeometry},
fs::FileSystem,
parsing::ast::types::{Annotation, Node},
source_range::SourceRange,
@ -29,7 +29,7 @@ use crate::{
pub const ZOO_COORD_SYSTEM: System = *KITTYCAD;
pub async fn import_foreign(
file_path: &Path,
file_path: &TypedPath,
format: Option<InputFormat3d>,
exec_state: &mut ExecState,
ctxt: &ExecutorContext,
@ -43,19 +43,18 @@ pub async fn import_foreign(
}));
}
let ext_format =
get_import_format_from_extension(file_path.extension().and_then(OsStr::to_str).ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: format!("No file extension found for `{}`", file_path.display()),
source_ranges: vec![source_range],
})
})?)
.map_err(|e| {
KclError::Semantic(KclErrorDetails {
message: e.to_string(),
source_ranges: vec![source_range],
})
})?;
let ext_format = get_import_format_from_extension(file_path.extension().ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: format!("No file extension found for `{}`", file_path.display()),
source_ranges: vec![source_range],
})
})?)
.map_err(|e| {
KclError::Semantic(KclErrorDetails {
message: e.to_string(),
source_ranges: vec![source_range],
})
})?;
// Get the format type from the extension of the file.
let format = if let Some(format) = format {
@ -80,15 +79,12 @@ pub async fn import_foreign(
})?;
// We want the file_path to be without the parent.
let file_name = std::path::Path::new(&file_path)
.file_name()
.map(|p| p.to_string_lossy().to_string())
.ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: format!("Could not get the file name from the path `{}`", file_path.display()),
source_ranges: vec![source_range],
})
})?;
let file_name = file_path.file_name().map(|p| p.to_string()).ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: format!("Could not get the file name from the path `{}`", file_path.display()),
source_ranges: vec![source_range],
})
})?;
let mut import_files = vec![kcmc::ImportFile {
path: file_name.to_string(),
data: file_contents.clone(),
@ -112,19 +108,12 @@ pub async fn import_foreign(
if let Some(uri) = &buffer.uri {
if !uri.starts_with("data:") {
// We want this path relative to the file_path given.
let bin_path = std::path::Path::new(&file_path)
.parent()
.map(|p| p.join(uri))
.map(|p| p.to_string_lossy().to_string())
.ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: format!(
"Could not get the parent path of the file `{}`",
file_path.display()
),
source_ranges: vec![source_range],
})
})?;
let bin_path = file_path.parent().map(|p| p.join(uri)).ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: format!("Could not get the parent path of the file `{}`", file_path.display()),
source_ranges: vec![source_range],
})
})?;
let bin_contents = ctxt.fs.read(&bin_path, source_range).await.map_err(|e| {
KclError::Semantic(KclErrorDetails {
@ -154,7 +143,7 @@ pub async fn import_foreign(
pub(super) fn format_from_annotations(
annotations: &[Node<Annotation>],
path: &Path,
path: &TypedPath,
import_source_range: SourceRange,
) -> Result<Option<InputFormat3d>, KclError> {
if annotations.is_empty() {
@ -184,7 +173,6 @@ pub(super) fn format_from_annotations(
let mut result = result
.or_else(|| {
path.extension()
.and_then(OsStr::to_str)
.and_then(|ext| get_import_format_from_extension(ext).ok())
})
.ok_or(KclError::Semantic(KclErrorDetails {
@ -397,7 +385,7 @@ mod test {
fn annotations() {
// no annotations
assert!(
format_from_annotations(&[], Path::new("../foo.txt"), SourceRange::default(),)
format_from_annotations(&[], &TypedPath::from("../foo.txt"), SourceRange::default(),)
.unwrap()
.is_none()
);
@ -406,7 +394,7 @@ mod test {
let text = "@()\nimport '../foo.gltf' as foo";
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
let attrs = parsed.body[0].get_attrs();
let fmt = format_from_annotations(attrs, Path::new("../foo.gltf"), SourceRange::default())
let fmt = format_from_annotations(attrs, &TypedPath::from("../foo.gltf"), SourceRange::default())
.unwrap()
.unwrap();
assert_eq!(
@ -418,7 +406,7 @@ mod test {
let text = "@(format = gltf)\nimport '../foo.txt' as foo";
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
let attrs = parsed.body[0].get_attrs();
let fmt = format_from_annotations(attrs, Path::new("../foo.txt"), SourceRange::default())
let fmt = format_from_annotations(attrs, &TypedPath::from("../foo.txt"), SourceRange::default())
.unwrap()
.unwrap();
assert_eq!(
@ -427,7 +415,7 @@ mod test {
);
// format, no extension (wouldn't parse but might some day)
let fmt = format_from_annotations(attrs, Path::new("../foo"), SourceRange::default())
let fmt = format_from_annotations(attrs, &TypedPath::from("../foo"), SourceRange::default())
.unwrap()
.unwrap();
assert_eq!(
@ -439,7 +427,7 @@ mod test {
let text = "@(format = obj, coords = vulkan, lengthUnit = ft)\nimport '../foo.txt' as foo";
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
let attrs = parsed.body[0].get_attrs();
let fmt = format_from_annotations(attrs, Path::new("../foo.txt"), SourceRange::default())
let fmt = format_from_annotations(attrs, &TypedPath::from("../foo.txt"), SourceRange::default())
.unwrap()
.unwrap();
assert_eq!(
@ -454,7 +442,7 @@ mod test {
let text = "@(coords = vulkan, lengthUnit = ft)\nimport '../foo.obj' as foo";
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
let attrs = parsed.body[0].get_attrs();
let fmt = format_from_annotations(attrs, Path::new("../foo.obj"), SourceRange::default())
let fmt = format_from_annotations(attrs, &TypedPath::from("../foo.obj"), SourceRange::default())
.unwrap()
.unwrap();
assert_eq!(
@ -507,7 +495,7 @@ mod test {
fn assert_annotation_error(src: &str, path: &str, expected: &str) {
let parsed = crate::Program::parse_no_errs(src).unwrap().ast;
let attrs = parsed.body[0].get_attrs();
let err = format_from_annotations(attrs, Path::new(path), SourceRange::default()).unwrap_err();
let err = format_from_annotations(attrs, &TypedPath::from(path), SourceRange::default()).unwrap_err();
assert!(
err.message().contains(expected),
"Expected: `{expected}`, found `{}`",

View File

@ -1,6 +1,6 @@
//! The executor for the AST.
use std::{path::PathBuf, sync::Arc};
use std::sync::Arc;
use anyhow::Result;
#[cfg(feature = "artifact-graph")]
@ -35,6 +35,7 @@ use crate::{
errors::{KclError, KclErrorDetails},
execution::{
cache::{CacheInformation, CacheResult},
typed_path::TypedPath,
types::{UnitAngle, UnitLen},
},
fs::FileManager,
@ -59,6 +60,7 @@ mod import;
pub(crate) mod kcl_value;
mod memory;
mod state;
pub mod typed_path;
pub(crate) mod types;
/// Outcome of executing a program. This is used in TS.
@ -287,10 +289,10 @@ pub struct ExecutorSettings {
pub replay: Option<String>,
/// The directory of the current project. This is used for resolving import
/// paths. If None is given, the current working directory is used.
pub project_directory: Option<PathBuf>,
pub project_directory: Option<TypedPath>,
/// This is the path to the current file being executed.
/// We use this for preventing cyclic imports.
pub current_file: Option<PathBuf>,
pub current_file: Option<TypedPath>,
}
impl Default for ExecutorSettings {
@ -360,15 +362,15 @@ impl From<crate::settings::types::project::ProjectModelingSettings> for Executor
impl ExecutorSettings {
/// Add the current file path to the executor settings.
pub fn with_current_file(&mut self, current_file: PathBuf) {
pub fn with_current_file(&mut self, current_file: TypedPath) {
// We want the parent directory of the file.
if current_file.extension() == Some(std::ffi::OsStr::new("kcl")) {
if current_file.extension() == Some("kcl") {
self.current_file = Some(current_file.clone());
// Get the parent directory.
if let Some(parent) = current_file.parent() {
self.project_directory = Some(parent.to_path_buf());
self.project_directory = Some(parent);
} else {
self.project_directory = Some(std::path::PathBuf::from(""));
self.project_directory = Some(TypedPath::from(""));
}
} else {
self.project_directory = Some(current_file.clone());
@ -856,7 +858,7 @@ impl ExecutorContext {
if universe_map.contains_key(value) {
exec_state.global.operations.push(Operation::GroupBegin {
group: Group::ModuleInstance {
name: value.file_name().unwrap_or_default().to_string_lossy().into_owned(),
name: value.file_name().unwrap_or_default(),
module_id,
},
source_range,
@ -1306,7 +1308,7 @@ pub(crate) async fn parse_execute(code: &str) -> Result<ExecTestResults, KclErro
#[cfg(test)]
pub(crate) async fn parse_execute_with_project_dir(
code: &str,
project_directory: Option<std::path::PathBuf>,
project_directory: Option<TypedPath>,
) -> Result<ExecTestResults, KclError> {
let program = crate::Program::parse_no_errs(code)?;

View File

@ -275,7 +275,7 @@ impl ExecState {
.mod_loader
.import_stack
.iter()
.map(|p| p.as_path().to_string_lossy())
.map(|p| p.to_string_lossy())
.collect::<Vec<_>>()
.join(" -> "),
path,

View File

@ -0,0 +1,208 @@
//! A typed path type so that in wasm we can track if its a windows or unix path.
//! On non-wasm platforms, this is just a std::path::PathBuf.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TypedPath(
#[cfg(target_arch = "wasm32")] pub typed_path::TypedPathBuf,
#[cfg(not(target_arch = "wasm32"))] pub std::path::PathBuf,
);
impl std::fmt::Display for TypedPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
#[cfg(target_arch = "wasm32")]
{
self.0.to_path().display().fmt(f)
}
#[cfg(not(target_arch = "wasm32"))]
{
self.0.display().fmt(f)
}
}
}
impl Default for TypedPath {
fn default() -> Self {
#[cfg(target_arch = "wasm32")]
{
TypedPath(typed_path::TypedPath::derive("").to_path_buf())
}
#[cfg(not(target_arch = "wasm32"))]
{
TypedPath(std::path::PathBuf::new())
}
}
}
impl From<&String> for TypedPath {
fn from(path: &String) -> Self {
#[cfg(target_arch = "wasm32")]
{
TypedPath(typed_path::TypedPath::derive(path).to_path_buf())
}
#[cfg(not(target_arch = "wasm32"))]
{
TypedPath(std::path::PathBuf::from(path))
}
}
}
impl From<&str> for TypedPath {
fn from(path: &str) -> Self {
#[cfg(target_arch = "wasm32")]
{
TypedPath(typed_path::TypedPath::derive(path).to_path_buf())
}
#[cfg(not(target_arch = "wasm32"))]
{
TypedPath(std::path::PathBuf::from(path))
}
}
}
impl TypedPath {
pub fn extension(&self) -> Option<&str> {
#[cfg(target_arch = "wasm32")]
{
self.0
.extension()
.map(|s| std::str::from_utf8(s).map(|s| s.trim_start_matches('.')).unwrap_or(""))
.filter(|s| !s.is_empty())
}
#[cfg(not(target_arch = "wasm32"))]
{
self.0.extension().and_then(|s| s.to_str())
}
}
pub fn join(&self, path: &str) -> Self {
#[cfg(target_arch = "wasm32")]
{
TypedPath(self.0.join(path))
}
#[cfg(not(target_arch = "wasm32"))]
{
TypedPath(self.0.join(path))
}
}
pub fn parent(&self) -> Option<Self> {
#[cfg(target_arch = "wasm32")]
{
self.0.parent().map(|p| TypedPath(p.to_path_buf()))
}
#[cfg(not(target_arch = "wasm32"))]
{
self.0.parent().map(|p| TypedPath(p.to_path_buf()))
}
}
pub fn to_string_lossy(&self) -> String {
#[cfg(target_arch = "wasm32")]
{
self.0.to_path().to_string_lossy().to_string()
}
#[cfg(not(target_arch = "wasm32"))]
{
self.0.to_string_lossy().to_string()
}
}
pub fn display(&self) -> String {
#[cfg(target_arch = "wasm32")]
{
self.0.to_path().display().to_string()
}
#[cfg(not(target_arch = "wasm32"))]
{
self.0.display().to_string()
}
}
pub fn file_name(&self) -> Option<String> {
#[cfg(target_arch = "wasm32")]
{
self.0
.file_name()
.map(|s| std::str::from_utf8(s).unwrap_or(""))
.filter(|s| !s.is_empty())
.map(|s| s.to_string())
}
#[cfg(not(target_arch = "wasm32"))]
{
self.0.file_name().and_then(|s| s.to_str()).map(|s| s.to_string())
}
}
}
impl serde::Serialize for TypedPath {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
#[cfg(target_arch = "wasm32")]
{
self.0.to_str().serialize(serializer)
}
#[cfg(not(target_arch = "wasm32"))]
{
self.0.serialize(serializer)
}
}
}
impl<'de> serde::de::Deserialize<'de> for TypedPath {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[cfg(target_arch = "wasm32")]
{
let path: String = serde::Deserialize::deserialize(deserializer)?;
Ok(TypedPath(typed_path::TypedPath::derive(&path).to_path_buf()))
}
#[cfg(not(target_arch = "wasm32"))]
{
let path: std::path::PathBuf = serde::Deserialize::deserialize(deserializer)?;
Ok(TypedPath(path))
}
}
}
impl ts_rs::TS for TypedPath {
type WithoutGenerics = Self;
fn name() -> String {
"string".to_string()
}
fn decl() -> String {
std::path::PathBuf::decl()
}
fn decl_concrete() -> String {
std::path::PathBuf::decl_concrete()
}
fn inline() -> String {
std::path::PathBuf::inline()
}
fn inline_flattened() -> String {
std::path::PathBuf::inline_flattened()
}
fn output_path() -> Option<&'static std::path::Path> {
std::path::PathBuf::output_path()
}
}
impl schemars::JsonSchema for TypedPath {
fn schema_name() -> String {
"TypedPath".to_owned()
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
// TODO: Actually generate a reasonable schema.
gen.subschema_for::<std::path::PathBuf>()
}
}

View File

@ -2,6 +2,7 @@
use anyhow::Result;
use crate::execution::typed_path::TypedPath;
use crate::{
errors::{KclError, KclErrorDetails},
fs::FileSystem,
@ -25,56 +26,44 @@ impl Default for FileManager {
#[async_trait::async_trait]
impl FileSystem for FileManager {
async fn read<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: SourceRange,
) -> Result<Vec<u8>, KclError> {
tokio::fs::read(&path).await.map_err(|e| {
async fn read(&self, path: &TypedPath, source_range: SourceRange) -> Result<Vec<u8>, KclError> {
tokio::fs::read(&path.0).await.map_err(|e| {
KclError::Io(KclErrorDetails {
message: format!("Failed to read file `{}`: {}", path.as_ref().display(), e),
message: format!("Failed to read file `{}`: {}", path.display(), e),
source_ranges: vec![source_range],
})
})
}
async fn read_to_string<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: SourceRange,
) -> Result<String, KclError> {
tokio::fs::read_to_string(&path).await.map_err(|e| {
async fn read_to_string(&self, path: &TypedPath, source_range: SourceRange) -> Result<String, KclError> {
tokio::fs::read_to_string(&path.0).await.map_err(|e| {
KclError::Io(KclErrorDetails {
message: format!("Failed to read file `{}`: {}", path.as_ref().display(), e),
message: format!("Failed to read file `{}`: {}", path.display(), e),
source_ranges: vec![source_range],
})
})
}
async fn exists<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: SourceRange,
) -> Result<bool, crate::errors::KclError> {
tokio::fs::metadata(&path).await.map(|_| true).or_else(|e| {
async fn exists(&self, path: &TypedPath, source_range: SourceRange) -> Result<bool, crate::errors::KclError> {
tokio::fs::metadata(&path.0).await.map(|_| true).or_else(|e| {
if e.kind() == std::io::ErrorKind::NotFound {
Ok(false)
} else {
Err(KclError::Io(KclErrorDetails {
message: format!("Failed to check if file `{}` exists: {}", path.as_ref().display(), e),
message: format!("Failed to check if file `{}` exists: {}", path.display(), e),
source_ranges: vec![source_range],
}))
}
})
}
async fn get_all_files<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
async fn get_all_files(
&self,
path: P,
path: &TypedPath,
source_range: SourceRange,
) -> Result<Vec<std::path::PathBuf>, crate::errors::KclError> {
) -> Result<Vec<TypedPath>, crate::errors::KclError> {
let mut files = vec![];
let mut stack = vec![path.as_ref().to_path_buf()];
let mut stack = vec![path.0.to_path_buf()];
while let Some(path) = stack.pop() {
if !path.is_dir() {
@ -94,7 +83,7 @@ impl FileSystem for FileManager {
// Iterate over the directory.
stack.push(path);
} else {
files.push(path);
files.push(TypedPath(path));
}
}
}

View File

@ -2,6 +2,7 @@
use anyhow::Result;
use crate::execution::typed_path::TypedPath;
use crate::SourceRange;
#[cfg(not(target_arch = "wasm32"))]
@ -20,30 +21,22 @@ pub use wasm::FileManager;
#[async_trait::async_trait]
pub trait FileSystem: Clone {
/// Read a file from the local file system.
async fn read<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: SourceRange,
) -> Result<Vec<u8>, crate::errors::KclError>;
async fn read(&self, path: &TypedPath, source_range: SourceRange) -> Result<Vec<u8>, crate::errors::KclError>;
/// Read a file from the local file system.
async fn read_to_string<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
async fn read_to_string(
&self,
path: P,
path: &TypedPath,
source_range: SourceRange,
) -> Result<String, crate::errors::KclError>;
/// Check if a file exists on the local file system.
async fn exists<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: SourceRange,
) -> Result<bool, crate::errors::KclError>;
async fn exists(&self, path: &TypedPath, source_range: SourceRange) -> Result<bool, crate::errors::KclError>;
/// Get all the files in a directory recursively.
async fn get_all_files<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
async fn get_all_files(
&self,
path: P,
path: &TypedPath,
source_range: SourceRange,
) -> Result<Vec<std::path::PathBuf>, crate::errors::KclError>;
) -> Result<Vec<TypedPath>, crate::errors::KclError>;
}

View File

@ -5,6 +5,7 @@ use wasm_bindgen::prelude::wasm_bindgen;
use crate::{
errors::{KclError, KclErrorDetails},
execution::typed_path::TypedPath,
fs::FileSystem,
wasm::JsFuture,
SourceRange,
@ -15,6 +16,9 @@ extern "C" {
#[derive(Debug, Clone)]
pub type FileSystemManager;
#[wasm_bindgen(constructor)]
pub fn new() -> FileSystemManager;
#[wasm_bindgen(method, js_name = readFile, catch)]
fn read_file(this: &FileSystemManager, path: String) -> Result<js_sys::Promise, js_sys::Error>;
@ -41,30 +45,13 @@ unsafe impl Sync for FileManager {}
#[async_trait::async_trait]
impl FileSystem for FileManager {
async fn read<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: SourceRange,
) -> Result<Vec<u8>, KclError> {
let promise = self
.manager
.read_file(
path.as_ref()
.to_str()
.ok_or_else(|| {
KclError::Engine(KclErrorDetails {
message: "Failed to convert path to string".to_string(),
source_ranges: vec![source_range],
})
})?
.to_string(),
)
.map_err(|e| {
KclError::Engine(KclErrorDetails {
message: e.to_string().into(),
source_ranges: vec![source_range],
})
})?;
async fn read(&self, path: &TypedPath, source_range: SourceRange) -> Result<Vec<u8>, KclError> {
let promise = self.manager.read_file(path.to_string_lossy()).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: e.to_string().into(),
source_ranges: vec![source_range],
})
})?;
let value = JsFuture::from(promise).await.map_err(|e| {
KclError::Engine(KclErrorDetails {
@ -79,11 +66,7 @@ impl FileSystem for FileManager {
Ok(bytes)
}
async fn read_to_string<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: SourceRange,
) -> Result<String, KclError> {
async fn read_to_string(&self, path: &TypedPath, source_range: SourceRange) -> Result<String, KclError> {
let bytes = self.read(path, source_range).await?;
let string = String::from_utf8(bytes).map_err(|e| {
KclError::Engine(KclErrorDetails {
@ -95,30 +78,13 @@ impl FileSystem for FileManager {
Ok(string)
}
async fn exists<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: SourceRange,
) -> Result<bool, crate::errors::KclError> {
let promise = self
.manager
.exists(
path.as_ref()
.to_str()
.ok_or_else(|| {
KclError::Engine(KclErrorDetails {
message: "Failed to convert path to string".to_string(),
source_ranges: vec![source_range],
})
})?
.to_string(),
)
.map_err(|e| {
KclError::Engine(KclErrorDetails {
message: e.to_string().into(),
source_ranges: vec![source_range],
})
})?;
async fn exists(&self, path: &TypedPath, source_range: SourceRange) -> Result<bool, crate::errors::KclError> {
let promise = self.manager.exists(path.to_string_lossy()).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: e.to_string().into(),
source_ranges: vec![source_range],
})
})?;
let value = JsFuture::from(promise).await.map_err(|e| {
KclError::Engine(KclErrorDetails {
@ -137,30 +103,17 @@ impl FileSystem for FileManager {
Ok(it_exists)
}
async fn get_all_files<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
async fn get_all_files(
&self,
path: P,
path: &TypedPath,
source_range: SourceRange,
) -> Result<Vec<std::path::PathBuf>, crate::errors::KclError> {
let promise = self
.manager
.get_all_files(
path.as_ref()
.to_str()
.ok_or_else(|| {
KclError::Engine(KclErrorDetails {
message: "Failed to convert path to string".to_string(),
source_ranges: vec![source_range],
})
})?
.to_string(),
)
.map_err(|e| {
KclError::Engine(KclErrorDetails {
message: e.to_string().into(),
source_ranges: vec![source_range],
})
})?;
) -> Result<Vec<TypedPath>, crate::errors::KclError> {
let promise = self.manager.get_all_files(path.to_string_lossy()).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: e.to_string().into(),
source_ranges: vec![source_range],
})
})?;
let value = JsFuture::from(promise).await.map_err(|e| {
KclError::Engine(KclErrorDetails {
@ -183,6 +136,6 @@ impl FileSystem for FileManager {
})
})?;
Ok(files.into_iter().map(std::path::PathBuf::from).collect())
Ok(files.into_iter().map(|s| TypedPath::from(&s)).collect())
}
}

View File

@ -90,6 +90,7 @@ pub use errors::{
};
pub use execution::{
bust_cache, clear_mem_cache,
typed_path::TypedPath,
types::{UnitAngle, UnitLen},
ExecOutcome, ExecState, ExecutorContext, ExecutorSettings, MetaSettings, Point2d,
};

View File

@ -11,6 +11,7 @@ use tower_lsp::lsp_types::{
TextDocumentItem, WorkspaceFolder,
};
use crate::execution::typed_path::TypedPath;
use crate::fs::FileSystem;
/// A trait for the backend of the language server.
@ -75,18 +76,13 @@ where
self.inner_on_change(params, false).await;
}
async fn update_from_disk<P: AsRef<std::path::Path> + std::marker::Send>(&self, path: P) -> Result<()> {
async fn update_from_disk(&self, path: &TypedPath) -> Result<()> {
// Read over all the files in the directory and add them to our current code map.
let files = self.fs().get_all_files(path.as_ref(), Default::default()).await?;
let files = self.fs().get_all_files(path, Default::default()).await?;
for file in files {
// Read the file.
let contents = self.fs().read(&file, Default::default()).await?;
let file_path = format!(
"file://{}",
file.as_path()
.to_str()
.ok_or_else(|| anyhow::anyhow!("could not get name of file: {:?}", file))?
);
let file_path = format!("file://{}", file.to_string_lossy());
self.insert_code_map(file_path, contents).await;
}
@ -139,7 +135,7 @@ where
}
for added in params.event.added {
// Try to read all the files in the project.
let project_dir = added.uri.to_string().replace("file://", "");
let project_dir = TypedPath::from(&added.uri.to_string().replace("file://", ""));
if let Err(err) = self.update_from_disk(&project_dir).await {
self.client()
.log_message(

View File

@ -1,4 +1,4 @@
use std::{fmt, path::PathBuf};
use std::fmt;
use anyhow::Result;
use schemars::JsonSchema;
@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
use crate::{
errors::{KclError, KclErrorDetails},
exec::KclValue,
execution::{EnvironmentRef, PreImportedGeometry},
execution::{typed_path::TypedPath, EnvironmentRef, PreImportedGeometry},
fs::{FileManager, FileSystem},
parsing::ast::types::{ImportPath, Node, Program},
source_range::SourceRange,
@ -46,7 +46,7 @@ impl std::fmt::Display for ModuleId {
pub(crate) struct ModuleLoader {
/// The stack of import statements for detecting circular module imports.
/// If this is empty, we're not currently executing an import statement.
pub import_stack: Vec<PathBuf>,
pub import_stack: Vec<TypedPath>,
}
impl ModuleLoader {
@ -63,7 +63,7 @@ impl ModuleLoader {
"circular import of modules is not allowed: {} -> {}",
self.import_stack
.iter()
.map(|p| p.as_path().to_string_lossy())
.map(|p| p.to_string_lossy())
.collect::<Vec<_>>()
.join(" -> "),
path,
@ -140,12 +140,12 @@ pub enum ModuleRepr {
pub enum ModulePath {
// The main file of the project.
Main,
Local { value: PathBuf },
Local { value: TypedPath },
Std { value: String },
}
impl ModulePath {
pub(crate) fn expect_path(&self) -> &PathBuf {
pub(crate) fn expect_path(&self) -> &TypedPath {
match self {
ModulePath::Local { value: p } => p,
_ => unreachable!(),
@ -180,13 +180,13 @@ impl ModulePath {
}
}
pub(crate) fn from_import_path(path: &ImportPath, project_directory: &Option<PathBuf>) -> Self {
pub(crate) fn from_import_path(path: &ImportPath, project_directory: &Option<TypedPath>) -> Self {
match path {
ImportPath::Kcl { filename: path } | ImportPath::Foreign { path } => {
let resolved_path = if let Some(project_dir) = project_directory {
project_dir.join(path)
} else {
std::path::PathBuf::from(path)
TypedPath::from(path)
};
ModulePath::Local { value: resolved_path }
}
@ -205,7 +205,7 @@ impl fmt::Display for ModulePath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ModulePath::Main => write!(f, "main"),
ModulePath::Local { value: path } => path.display().fmt(f),
ModulePath::Local { value: path } => path.fmt(f),
ModulePath::Std { value: s } => write!(f, "std::{s}"),
}
}

View File

@ -134,7 +134,7 @@ pub async fn new_context(with_auth: bool, current_file: Option<PathBuf>) -> Resu
current_file: None,
};
if let Some(current_file) = current_file {
settings.with_current_file(current_file);
settings.with_current_file(crate::TypedPath(current_file));
}
let ctx = ExecutorContext::new(&client, settings)
.await

View File

@ -1,6 +1,5 @@
use std::{
collections::HashMap,
path::PathBuf,
sync::{Arc, Mutex},
};
@ -8,6 +7,7 @@ use anyhow::Result;
use crate::{
errors::KclErrorDetails,
execution::typed_path::TypedPath,
modules::{ModulePath, ModuleRepr},
parsing::ast::types::{ImportPath, ImportStatement, Node as AstNode},
walk::{Node, Visitable},
@ -22,7 +22,7 @@ type Dependency = (String, String);
type Graph = Vec<Dependency>;
pub(crate) type DependencyInfo = (AstNode<ImportStatement>, ModuleId, ModulePath, ModuleRepr);
pub(crate) type UniverseMap = HashMap<PathBuf, AstNode<ImportStatement>>;
pub(crate) type UniverseMap = HashMap<TypedPath, AstNode<ImportStatement>>;
pub(crate) type Universe = HashMap<String, DependencyInfo>;
/// Process a number of programs, returning the graph of dependencies.

View File

@ -1,7 +1,5 @@
//! Web assembly utils.
pub mod vitest;
use std::{
pin::Pin,
task::{Context, Poll},

View File

@ -1,43 +0,0 @@
use js_sys::Reflect;
use wasm_bindgen::JsValue;
/// returns true if globalThis.process?.env?.VITEST is truthy
fn is_vitest_by_env() -> bool {
let global = js_sys::global();
// global.process
let process = Reflect::get(&global, &JsValue::from_str("process"))
.ok()
.unwrap_or_else(|| JsValue::NULL);
// process.env
let env = Reflect::get(&process, &JsValue::from_str("env"))
.ok()
.unwrap_or_else(|| JsValue::NULL);
// env.VITEST
let vitest = Reflect::get(&env, &JsValue::from_str("VITEST"))
.ok()
.unwrap_or_else(|| JsValue::NULL);
// "true", "1", or a boolean
vitest
.as_bool()
.unwrap_or_else(|| vitest.as_string().map_or(false, |s| s == "true" || s == "1"))
}
fn is_vitest_by_global() -> bool {
let global = js_sys::global();
Reflect::has(&global, &JsValue::from_str("__vitest_worker__")).unwrap_or(false)
}
pub fn running_in_vitest() -> bool {
let running_in_vitest = is_vitest_by_env() || is_vitest_by_global();
if running_in_vitest {
web_sys::console::log_1(&JsValue::from_str(&format!(
"running_in_vitest: {}, SOME BEHAVIOR MIGHT BE DIFFERENT THAN THE WASM IN THE APP",
running_in_vitest
)));
}
running_in_vitest
}