From 9ebc0c1604067cc312ffc09428eeebbf4db6917b Mon Sep 17 00:00:00 2001 From: ffrostfall Date: Mon, 4 Aug 2025 13:27:02 -0400 Subject: [PATCH 1/5] Add absolute path support to roblox string requires --- src/rules/convert_require/rojo_sourcemap.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/rules/convert_require/rojo_sourcemap.rs b/src/rules/convert_require/rojo_sourcemap.rs index 3fda9f96..65f27279 100644 --- a/src/rules/convert_require/rojo_sourcemap.rs +++ b/src/rules/convert_require/rojo_sourcemap.rs @@ -1,4 +1,4 @@ -use std::path::{Path, PathBuf}; +use std::path::{self, Path, PathBuf}; use serde::{Deserialize, Serialize}; @@ -31,7 +31,9 @@ impl RojoSourcemapNode { while let Some(node) = queue.pop() { node.id = index; for file_path in &mut node.file_paths { - *file_path = utils::normalize_path(relative_to.join(&file_path)); + if file_path.is_relative() { + *file_path = utils::normalize_path(relative_to.join(&file_path)); + } } for child in &mut node.children { child.parent_id = index; @@ -215,10 +217,17 @@ impl RojoSourcemap { ids } - fn find_node(&self, path: &Path) -> Option<&RojoSourcemapNode> { - self.root_node - .iter() - .find(|node| node.file_paths.iter().any(|file_path| file_path == path)) + fn find_node(&self, target_path: &Path) -> Option<&RojoSourcemapNode> { + self.root_node.iter().find(|node| { + node.file_paths.iter().any(|file_path| { + if file_path.is_absolute() { + file_path.to_path_buf() + == path::absolute(target_path).expect("failed to convert") + } else { + file_path == target_path + } + }) + }) } } From 8864076b480ea5f2918eaa592ccbcc46b0eca2aa Mon Sep 17 00:00:00 2001 From: ffrostfall Date: Tue, 5 Aug 2025 22:50:49 -0400 Subject: [PATCH 2/5] Absolute path support pt. 2 --- .../convert_require/roblox_require_mode.rs | 4 + src/rules/convert_require/rojo_sourcemap.rs | 252 +++++++++++++++++- 2 files changed, 254 insertions(+), 2 deletions(-) diff --git a/src/rules/convert_require/roblox_require_mode.rs b/src/rules/convert_require/roblox_require_mode.rs index 43821ef9..90dbc66d 100644 --- a/src/rules/convert_require/roblox_require_mode.rs +++ b/src/rules/convert_require/roblox_require_mode.rs @@ -214,6 +214,10 @@ fn get_relative_path( source_path: &Path, use_current_dir_prefix: bool, ) -> Result, DarkluaError> { + if require_path.is_absolute() { + return Ok(Some(require_path.to_path_buf())); + } + Ok( pathdiff::diff_paths(require_path, get_relative_parent_path(source_path)) .map(|path| { diff --git a/src/rules/convert_require/rojo_sourcemap.rs b/src/rules/convert_require/rojo_sourcemap.rs index 65f27279..c5843f80 100644 --- a/src/rules/convert_require/rojo_sourcemap.rs +++ b/src/rules/convert_require/rojo_sourcemap.rs @@ -1,4 +1,4 @@ -use std::path::{self, Path, PathBuf}; +use std::path::{self, Component, Path, PathBuf}; use serde::{Deserialize, Serialize}; @@ -23,6 +23,40 @@ struct RojoSourcemapNode { parent_id: NodeId, } +pub fn normalize_path(path: &Path) -> PathBuf { + let mut components = path.components().peekable(); + let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { + components.next(); + PathBuf::from(c.as_os_str()) + } else { + PathBuf::new() + }; + + for component in components { + match component { + Component::Prefix(..) => unreachable!(), + Component::RootDir => { + ret.push(Component::RootDir); + } + Component::CurDir => {} + Component::ParentDir => { + if ret.ends_with(Component::ParentDir) { + ret.push(Component::ParentDir); + } else { + let popped = ret.pop(); + if !popped && !ret.has_root() { + ret.push(Component::ParentDir); + } + } + } + Component::Normal(c) => { + ret.push(c); + } + } + } + ret +} + impl RojoSourcemapNode { fn initialize(mut self, relative_to: &Path) -> Self { let mut queue = vec![&mut self]; @@ -126,8 +160,25 @@ impl RojoSourcemap { let from_file = from_file.as_ref(); let target_file = target_file.as_ref(); + let binding = from_file.to_path_buf().join(target_file); + + let a = normalize_path(binding.as_path()); + let from_node = self.find_node(from_file)?; - let target_node = self.find_node(target_file)?; + let target_node = self.find_node(if from_file.is_absolute() { + if from_file.is_absolute() { + log::trace!( + "in absolute rqeuire mode, normalized: {} -> {}", + from_file.display(), + a.display() + ); + a.as_path() + } else { + target_file + } + } else { + target_file + })?; let from_ancestors = self.hierarchy(from_node); let target_ancestors = self.hierarchy(target_node); @@ -282,6 +333,8 @@ mod test { ); } + // Relative + #[test] fn from_sibling_to_sibling_module() { let sourcemap = new_sourcemap( @@ -400,5 +453,200 @@ mod test { script_path(&["parent"]) ); } + + // Absolute + + #[test] + fn abs_from_sibling_to_sibling_module() { + let sourcemap = new_sourcemap( + r#"{ + "name": "Project", + "className": "ModuleScript", + "filePaths": ["C:/projects/thing/src/init.lua", "C:/projects/thing/default.project.json"], + "children": [ + { + "name": "main", + "className": "ModuleScript", + "filePaths": ["C:/projects/thing/src/main.lua"] + }, + { + "name": "value", + "className": "ModuleScript", + "filePaths": ["C:/projects/thing/src/value.lua"] + } + ] + }"#, + ); + pretty_assertions::assert_eq!( + sourcemap + .get_instance_path( + "C:/projects/thing/src/main.lua", + "C:/projects/thing/src/value.lua" + ) + .unwrap(), + script_path(&["parent", "value"]) + ); + } + + #[test] + fn abs_from_sibling_to_nested_sibling_module() { + let sourcemap = new_sourcemap( + r#"{ + "name": "Project", + "className": "ModuleScript", + "filePaths": ["C:/projects/thing/src/init.lua", "C:/projects/thing/default.project.json"], + "children": [ + { + "name": "main", + "className": "ModuleScript", + "filePaths": ["C:/projects/thing/src/main.lua"] + }, + { + "name": "Lib", + "className": "Folder", + "children": [ + { + "name": "format", + "className": "ModuleScript", + "filePaths": ["C:/projects/thing/src/Lib/format.lua"] + } + ] + } + ] + }"#, + ); + pretty_assertions::assert_eq!( + sourcemap + .get_instance_path( + "C:/projects/thing/src/main.lua", + "C:/projects/thing/src/Lib/format.lua" + ) + .unwrap(), + script_path(&["parent", "Lib", "format"]) + ); + } + + #[test] + fn abs_from_child_require_parent() { + let sourcemap = new_sourcemap( + r#"{ + "name": "Project", + "className": "ModuleScript", + "filePaths": ["C:/projects/thing/src/init.lua", "C:/projects/thing/default.project.json"], + "children": [ + { + "name": "main", + "className": "ModuleScript", + "filePaths": ["C:/projects/thing/src/main.lua"] + } + ] + }"#, + ); + pretty_assertions::assert_eq!( + sourcemap + .get_instance_path( + "C:/projects/thing/src/main.lua", + "C:/projects/thing/src/init.lua" + ) + .unwrap(), + script_path(&["parent"]) + ); + } + + #[test] + fn abs_from_child_require_parent_nested() { + let sourcemap = new_sourcemap( + r#"{ + "name": "Project", + "className": "ModuleScript", + "filePaths": ["C:/projects/thing/src/init.lua", "C:/projects/thing/default.project.json"], + "children": [ + { + "name": "Sub", + "className": "ModuleScript", + "filePaths": ["C:/projects/thing/src/Sub/init.lua"], + "children": [ + { + "name": "test", + "className": "ModuleScript", + "filePaths": ["C:/projects/thing/src/Sub/test.lua"] + } + ] + } + ] + }"#, + ); + pretty_assertions::assert_eq!( + sourcemap + .get_instance_path( + "C:/projects/thing/src/Sub/test.lua", + "C:/projects/thing/src/Sub/init.lua" + ) + .unwrap(), + script_path(&["parent"]) + ); + } + + #[test] + fn rel_from_absolute() { + let sourcemap = new_sourcemap( + r#"{ + "name": "Project", + "className": "ModuleScript", + "filePaths": ["C:/projects/thing/src/init.luau", "C:/projects/thing/default.project.json"], + "children": [ + { + "name": "Sub", + "className": "ModuleScript", + "filePaths": ["C:/projects/thing/src/Sub/init.luau"], + "children": [ + { + "name": "test", + "className": "ModuleScript", + "filePaths": ["C:/projects/thing/src/Sub/test.luau"] + } + ] + } + ] + }"#, + ); + pretty_assertions::assert_eq!( + sourcemap + .get_instance_path("C:/projects/thing/src/Sub/test.luau", "../init.luau") + .unwrap(), + script_path(&["parent"]) + ); + } + + #[test] + fn nested_rel_from_absolute() { + let sourcemap = new_sourcemap( + r#"{ + "name": "Project", + "className": "ModuleScript", + "filePaths": ["C:/projects/thing/src/init.luau", "C:/projects/thing/default.project.json"], + "children": [ + { + "name": "Sub", + "className": "ModuleScript", + "filePaths": ["C:/projects/thing/src/Sub/init.luau"], + "children": [ + { + "name": "test", + "className": "ModuleScript", + "filePaths": ["C:/projects/thing/src/Sub/test.luau"] + } + ] + } + ] + }"#, + ); + pretty_assertions::assert_eq!( + sourcemap + .get_instance_path("C:/projects/thing/src/Sub/test.luau", "../init.luau") + .unwrap(), + script_path(&["parent"]) + ); + } } } From cfa39a454b40d39edfcb35b96c7dec90eb91eb71 Mon Sep 17 00:00:00 2001 From: ffrostfall Date: Mon, 11 Aug 2025 20:20:32 -0400 Subject: [PATCH 3/5] Use darklua normalize_path --- src/rules/convert_require/rojo_sourcemap.rs | 37 ++------------------- 1 file changed, 2 insertions(+), 35 deletions(-) diff --git a/src/rules/convert_require/rojo_sourcemap.rs b/src/rules/convert_require/rojo_sourcemap.rs index 8627a3b3..d8bf1cb8 100644 --- a/src/rules/convert_require/rojo_sourcemap.rs +++ b/src/rules/convert_require/rojo_sourcemap.rs @@ -1,7 +1,8 @@ -use std::path::{self, Component, Path, PathBuf}; +use std::path::{self, Path, PathBuf}; use serde::{Deserialize, Serialize}; +use crate::utils::normalize_path; use crate::{utils, DarkluaError}; use super::InstancePath; @@ -23,40 +24,6 @@ struct RojoSourcemapNode { parent_id: NodeId, } -pub fn normalize_path(path: &Path) -> PathBuf { - let mut components = path.components().peekable(); - let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { - components.next(); - PathBuf::from(c.as_os_str()) - } else { - PathBuf::new() - }; - - for component in components { - match component { - Component::Prefix(..) => unreachable!(), - Component::RootDir => { - ret.push(Component::RootDir); - } - Component::CurDir => {} - Component::ParentDir => { - if ret.ends_with(Component::ParentDir) { - ret.push(Component::ParentDir); - } else { - let popped = ret.pop(); - if !popped && !ret.has_root() { - ret.push(Component::ParentDir); - } - } - } - Component::Normal(c) => { - ret.push(c); - } - } - } - ret -} - impl RojoSourcemapNode { fn initialize(mut self, relative_to: &Path) -> Self { let mut queue = vec![&mut self]; From 9175ae0028bef2127347b01fc0dbd84adc2dd26e Mon Sep 17 00:00:00 2001 From: ffrostfall Date: Thu, 21 Aug 2025 10:20:09 -0400 Subject: [PATCH 4/5] remove redundant pathbuf, switch to has_root --- src/rules/convert_require/rojo_sourcemap.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rules/convert_require/rojo_sourcemap.rs b/src/rules/convert_require/rojo_sourcemap.rs index d8bf1cb8..0d14d61c 100644 --- a/src/rules/convert_require/rojo_sourcemap.rs +++ b/src/rules/convert_require/rojo_sourcemap.rs @@ -131,13 +131,13 @@ impl RojoSourcemap { let from_file = from_file.as_ref(); let target_file = target_file.as_ref(); - let binding = from_file.to_path_buf().join(target_file); + let binding = from_file.join(target_file); let normalized = normalize_path(binding.as_path()); let from_node = self.find_node(from_file)?; let target_node = self.find_node(if from_file.is_absolute() { - if from_file.is_absolute() { + if from_file.has_root() { log::trace!( "in absolute rqeuire mode, normalized: {} -> {}", from_file.display(), From c2a2b8caf405ea49a2e5bc94bfc004dfe3a682aa Mon Sep 17 00:00:00 2001 From: ffrostfall Date: Thu, 21 Aug 2025 12:16:56 -0400 Subject: [PATCH 5/5] Improve code --- src/rules/convert_require/rojo_sourcemap.rs | 45 ++++++++++----------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/src/rules/convert_require/rojo_sourcemap.rs b/src/rules/convert_require/rojo_sourcemap.rs index 0d14d61c..8e4902c3 100644 --- a/src/rules/convert_require/rojo_sourcemap.rs +++ b/src/rules/convert_require/rojo_sourcemap.rs @@ -33,7 +33,9 @@ impl RojoSourcemapNode { node.id = index; for file_path in &mut node.file_paths { if file_path.is_relative() { - *file_path = utils::normalize_path(relative_to.join(&file_path)); + *file_path = + path::absolute(utils::normalize_path(relative_to.join(&file_path))) + .expect("failed to convert to absolute path"); } } for child in &mut node.children { @@ -131,22 +133,16 @@ impl RojoSourcemap { let from_file = from_file.as_ref(); let target_file = target_file.as_ref(); - let binding = from_file.join(target_file); - - let normalized = normalize_path(binding.as_path()); + let normalized = normalize_path(from_file.join(target_file)); let from_node = self.find_node(from_file)?; - let target_node = self.find_node(if from_file.is_absolute() { - if from_file.has_root() { - log::trace!( - "in absolute rqeuire mode, normalized: {} -> {}", - from_file.display(), - normalized.display() - ); - normalized.as_path() - } else { - target_file - } + let target_node = self.find_node(if from_file.has_root() { + log::trace!( + "in absolute require mode, normalized: {} -> {}", + from_file.display(), + normalized.display() + ); + normalized.as_path() } else { target_file })?; @@ -240,15 +236,18 @@ impl RojoSourcemap { } fn find_node(&self, target_path: &Path) -> Option<&RojoSourcemapNode> { + let needle_path_buf = if !target_path.has_root() { + path::absolute(target_path).expect("failed") + } else { + target_path.to_path_buf() + }; + + let needle_path = needle_path_buf.as_path(); + self.root_node.iter().find(|node| { - node.file_paths.iter().any(|file_path| { - if file_path.is_absolute() { - file_path.to_path_buf() - == path::absolute(target_path).expect("failed to convert") - } else { - file_path == target_path - } - }) + node.file_paths + .iter() + .any(|file_path| file_path.as_path() == needle_path) }) } }