darklua_core/rules/convert_require/
roblox_require_mode.rs1use serde::{Deserialize, Serialize};
2
3use crate::{
4 frontend::DarkluaResult,
5 nodes::{Arguments, FunctionCall, Prefix},
6 rules::{
7 convert_require::rojo_sourcemap::RojoSourcemap,
8 require::path_utils::{get_relative_parent_path, get_relative_path},
9 Context,
10 },
11 utils, DarkluaError,
12};
13
14use std::path::{Component, Path, PathBuf};
15
16use super::{
17 instance_path::{get_parent_instance, script_identifier},
18 RequireMode, RobloxIndexStyle,
19};
20
21#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
22#[serde(deny_unknown_fields, rename_all = "snake_case")]
23pub struct RobloxRequireMode {
24 rojo_sourcemap: Option<PathBuf>,
25 #[serde(default, deserialize_with = "crate::utils::string_or_struct")]
26 indexing_style: RobloxIndexStyle,
27 #[serde(skip)]
28 cached_sourcemap: Option<RojoSourcemap>,
29}
30
31impl RobloxRequireMode {
32 pub(crate) fn initialize(&mut self, context: &Context) -> DarkluaResult<()> {
33 if let Some(ref rojo_sourcemap_path) = self
34 .rojo_sourcemap
35 .as_ref()
36 .map(|rojo_sourcemap_path| context.project_location().join(rojo_sourcemap_path))
37 {
38 context.add_file_dependency(rojo_sourcemap_path.clone());
39
40 let sourcemap_parent_location = get_relative_parent_path(rojo_sourcemap_path);
41 let sourcemap = RojoSourcemap::parse(
42 &context
43 .resources()
44 .get(rojo_sourcemap_path)
45 .map_err(|err| {
46 DarkluaError::from(err).context("while initializing Roblox require mode")
47 })?,
48 sourcemap_parent_location,
49 )
50 .map_err(|err| {
51 err.context(format!(
52 "unable to parse Rojo sourcemap at `{}`",
53 rojo_sourcemap_path.display()
54 ))
55 })?;
56 self.cached_sourcemap = Some(sourcemap);
57 }
58 Ok(())
59 }
60
61 pub(crate) fn find_require(
62 &self,
63 _call: &FunctionCall,
64 _context: &Context,
65 ) -> DarkluaResult<Option<PathBuf>> {
66 Err(DarkluaError::custom("unsupported initial require mode")
67 .context("Roblox require mode cannot be used as the current require mode"))
68 }
69
70 pub(crate) fn generate_require(
71 &self,
72 require_path: &Path,
73 current: &RequireMode,
74 context: &Context,
75 ) -> DarkluaResult<Option<Arguments>> {
76 let source_path = utils::normalize_path(context.current_path());
77 log::trace!(
78 "generate Roblox require for `{}` from `{}`",
79 require_path.display(),
80 source_path.display(),
81 );
82
83 if let Some((sourcemap, sourcemap_path)) = self
84 .cached_sourcemap
85 .as_ref()
86 .zip(self.rojo_sourcemap.as_ref())
87 {
88 if let Some(require_relative_to_sourcemap) = get_relative_path(
89 require_path,
90 get_relative_parent_path(sourcemap_path),
91 false,
92 )? {
93 log::trace!(
94 " ⨽ use sourcemap at `{}` to find `{}`",
95 sourcemap_path.display(),
96 require_relative_to_sourcemap.display()
97 );
98
99 if let Some(instance_path) =
100 sourcemap.get_instance_path(&source_path, &require_relative_to_sourcemap)
101 {
102 Ok(Some(Arguments::default().with_argument(
103 instance_path.convert(&self.indexing_style),
104 )))
105 } else {
106 match (
107 sourcemap.exists(&source_path),
108 sourcemap.exists(&require_relative_to_sourcemap),
109 ) {
110 (true, true) => {
111 log::warn!(
112 "unable to get relative path to `{}` in sourcemap (from `{}`)",
113 require_relative_to_sourcemap.display(),
114 source_path.display()
115 );
116 }
117 (false, _) => {
118 log::warn!(
119 "unable to find source path `{}` in sourcemap",
120 source_path.display()
121 );
122 }
123 (true, false) => {
124 log::warn!(
125 "unable to find path `{}` in sourcemap (from `{}`)",
126 require_relative_to_sourcemap.display(),
127 source_path.display()
128 );
129 }
130 }
131 Ok(None)
132 }
133 } else {
134 log::debug!(
135 "unable to get relative path from sourcemap for `{}`",
136 require_path.display()
137 );
138 Ok(None)
139 }
140 } else if let Some(relative_require_path) =
141 get_relative_path(require_path, &source_path, true)?
142 {
143 log::trace!(
144 "make require path relative to source: `{}`",
145 relative_require_path.display()
146 );
147
148 let require_is_module_folder_name =
149 current.is_module_folder_name(&relative_require_path);
150 let take_components = relative_require_path
153 .components()
154 .count()
155 .saturating_sub(if require_is_module_folder_name { 1 } else { 0 });
156 let mut path_components = relative_require_path.components().take(take_components);
157
158 if let Some(first_component) = path_components.next() {
159 let source_is_module_folder_name = current.is_module_folder_name(&source_path);
160
161 let instance_path = path_components.try_fold(
162 match first_component {
163 Component::CurDir => {
164 if source_is_module_folder_name {
165 script_identifier().into()
166 } else {
167 get_parent_instance(script_identifier())
168 }
169 }
170 Component::ParentDir => {
171 if source_is_module_folder_name {
172 get_parent_instance(script_identifier())
173 } else {
174 get_parent_instance(get_parent_instance(script_identifier()))
175 }
176 }
177 Component::Normal(_) => {
178 return Err(DarkluaError::custom(format!(
179 concat!(
180 "unable to convert path `{}`: the require path should be ",
181 "relative and start with `.` or `..` (got `{}`)"
182 ),
183 require_path.display(),
184 relative_require_path.display(),
185 )))
186 }
187 Component::Prefix(_) | Component::RootDir => {
188 return Err(DarkluaError::custom(format!(
189 concat!(
190 "unable to convert absolute path `{}`: ",
191 "without a provided Rojo sourcemap, ",
192 "darklua can only convert relative paths ",
193 "(starting with `.` or `..`)"
194 ),
195 require_path.display(),
196 )))
197 }
198 },
199 |instance: Prefix, component| match component {
200 Component::CurDir => Ok(instance),
201 Component::ParentDir => Ok(get_parent_instance(instance)),
202 Component::Normal(name) => utils::convert_os_string(name)
203 .map(|child_name| self.indexing_style.index(instance, child_name)),
204 Component::Prefix(_) | Component::RootDir => {
205 Err(DarkluaError::custom(format!(
206 "unable to convert path `{}`: unexpected component in relative path `{}`",
207 require_path.display(),
208 relative_require_path.display(),
209 )))
210 },
211 },
212 )?;
213
214 Ok(Some(Arguments::default().with_argument(instance_path)))
215 } else {
216 Err(DarkluaError::custom(format!(
217 "unable to convert path `{}` from `{}` without a sourcemap: the relative path is empty `{}`",
218 require_path.display(),
219 source_path.display(),
220 relative_require_path.display(),
221 )))
222 }
223 } else {
224 Err(DarkluaError::custom(format!(
225 concat!(
226 "unable to convert path `{}` from `{}` without a sourcemap: unable to ",
227 "make the require path relative to the source file"
228 ),
229 require_path.display(),
230 source_path.display(),
231 )))
232 }
233 }
234}