Skip to content

Commit 55c07f3

Browse files
committed
Some minor changes in Luau TextRequirer (comments, naming, etc)
1 parent ef4eabd commit 55c07f3

File tree

2 files changed

+50
-28
lines changed

2 files changed

+50
-28
lines changed

src/luau/require.rs

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::state::{callback_error_ext, Lua};
1414
use crate::table::Table;
1515
use crate::types::MaybeSend;
1616

17-
/// An error that can occur during navigation in the Luau `require` system.
17+
/// An error that can occur during navigation in the Luau `require-by-string` system.
1818
#[cfg(any(feature = "luau", doc))]
1919
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
2020
#[derive(Debug, Clone)]
@@ -50,7 +50,7 @@ impl From<Error> for NavigateError {
5050
#[cfg(feature = "luau")]
5151
type WriteResult = ffi::luarequire_WriteResult;
5252

53-
/// A trait for handling modules loading and navigation in the Luau `require` system.
53+
/// A trait for handling modules loading and navigation in the Luau `require-by-string` system.
5454
#[cfg(any(feature = "luau", doc))]
5555
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
5656
pub trait Require: MaybeSend {
@@ -103,16 +103,26 @@ impl fmt::Debug for dyn Require {
103103
}
104104
}
105105

106-
/// The standard implementation of Luau `require` navigation.
107-
#[doc(hidden)]
106+
/// The standard implementation of Luau `require-by-string` navigation.
108107
#[derive(Default, Debug)]
109108
pub struct TextRequirer {
109+
/// An absolute path to the current Luau module (not mapped to a physical file)
110110
abs_path: PathBuf,
111+
/// A relative path to the current Luau module (not mapped to a physical file)
111112
rel_path: PathBuf,
112-
module_path: PathBuf,
113+
/// A physical path to the current Luau module, which is a file or a directory with an
114+
/// `init.lua(u)` file
115+
resolved_path: Option<PathBuf>,
113116
}
114117

115118
impl TextRequirer {
119+
/// The prefix used for chunk names in the require system.
120+
/// Only chunk names starting with this prefix are allowed to be used in `require`.
121+
const CHUNK_PREFIX: &str = "@";
122+
123+
/// The file extensions that are considered valid for Luau modules.
124+
const FILE_EXTENSIONS: &[&str] = &["luau", "lua"];
125+
116126
/// Creates a new `TextRequirer` instance.
117127
pub fn new() -> Self {
118128
Self::default()
@@ -156,44 +166,48 @@ impl TextRequirer {
156166
components.into_iter().collect()
157167
}
158168

159-
fn find_module(path: &Path) -> StdResult<PathBuf, NavigateError> {
169+
/// Resolve a Luau module path to a physical file or directory.
170+
///
171+
/// Empty directories without init files are considered valid as "intermediate" directories.
172+
fn resolve_module(path: &Path) -> StdResult<Option<PathBuf>, NavigateError> {
160173
let mut found_path = None;
161174

162175
if path.components().next_back() != Some(Component::Normal("init".as_ref())) {
163176
let current_ext = (path.extension().and_then(|s| s.to_str()))
164177
.map(|s| format!("{s}."))
165178
.unwrap_or_default();
166-
for ext in ["luau", "lua"] {
179+
for ext in Self::FILE_EXTENSIONS {
167180
let candidate = path.with_extension(format!("{current_ext}{ext}"));
168181
if candidate.is_file() && found_path.replace(candidate).is_some() {
169182
return Err(NavigateError::Ambiguous);
170183
}
171184
}
172185
}
173186
if path.is_dir() {
174-
for component in ["init.luau", "init.lua"] {
187+
for component in Self::FILE_EXTENSIONS.iter().map(|ext| format!("init.{ext}")) {
175188
let candidate = path.join(component);
176189
if candidate.is_file() && found_path.replace(candidate).is_some() {
177190
return Err(NavigateError::Ambiguous);
178191
}
179192
}
180193

181194
if found_path.is_none() {
182-
found_path = Some(PathBuf::new());
195+
// Directories without init files are considered valid "intermediate" path
196+
return Ok(None);
183197
}
184198
}
185199

186-
found_path.ok_or(NavigateError::NotFound)
200+
Ok(Some(found_path.ok_or(NavigateError::NotFound)?))
187201
}
188202
}
189203

190204
impl Require for TextRequirer {
191205
fn is_require_allowed(&self, chunk_name: &str) -> bool {
192-
chunk_name.starts_with('@')
206+
chunk_name.starts_with(Self::CHUNK_PREFIX)
193207
}
194208

195209
fn reset(&mut self, chunk_name: &str) -> StdResult<(), NavigateError> {
196-
if !chunk_name.starts_with('@') {
210+
if !chunk_name.starts_with(Self::CHUNK_PREFIX) {
197211
return Err(NavigateError::NotFound);
198212
}
199213
let chunk_name = Self::normalize_chunk_name(&chunk_name[1..]);
@@ -205,74 +219,79 @@ impl Require for TextRequirer {
205219
let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?;
206220
self.abs_path = Self::normalize_path(&cwd.join(chunk_filename));
207221
self.rel_path = ([Component::CurDir, Component::Normal(chunk_filename)].into_iter()).collect();
208-
self.module_path = PathBuf::new();
222+
self.resolved_path = None;
209223

210224
return Ok(());
211225
}
212226

213227
if chunk_path.is_absolute() {
214-
let module_path = Self::find_module(&chunk_path)?;
228+
let resolved_path = Self::resolve_module(&chunk_path)?;
215229
self.abs_path = chunk_path.clone();
216230
self.rel_path = chunk_path;
217-
self.module_path = module_path;
231+
self.resolved_path = resolved_path;
218232
} else {
219233
// Relative path
220234
let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?;
221235
let abs_path = Self::normalize_path(&cwd.join(&chunk_path));
222-
let module_path = Self::find_module(&abs_path)?;
236+
let resolved_path = Self::resolve_module(&abs_path)?;
223237
self.abs_path = abs_path;
224238
self.rel_path = chunk_path;
225-
self.module_path = module_path;
239+
self.resolved_path = resolved_path;
226240
}
227241

228242
Ok(())
229243
}
230244

231245
fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError> {
232246
let path = Self::normalize_path(path.as_ref());
233-
let module_path = Self::find_module(&path)?;
247+
let resolved_path = Self::resolve_module(&path)?;
234248

235249
self.abs_path = path.clone();
236250
self.rel_path = path;
237-
self.module_path = module_path;
251+
self.resolved_path = resolved_path;
238252

239253
Ok(())
240254
}
241255

242256
fn to_parent(&mut self) -> StdResult<(), NavigateError> {
243257
let mut abs_path = self.abs_path.clone();
244258
if !abs_path.pop() {
259+
// It's important to return `NotFound` if we reached the root, as it's a "recoverable" error if we
260+
// cannot go beyond the root directory.
261+
// Luau "require-by-string` has a special logic to search for config file to resolve aliases.
245262
return Err(NavigateError::NotFound);
246263
}
247264
let mut rel_parent = self.rel_path.clone();
248265
rel_parent.pop();
249-
let module_path = Self::find_module(&abs_path)?;
266+
let resolved_path = Self::resolve_module(&abs_path)?;
250267

251268
self.abs_path = abs_path;
252269
self.rel_path = Self::normalize_path(&rel_parent);
253-
self.module_path = module_path;
270+
self.resolved_path = resolved_path;
254271

255272
Ok(())
256273
}
257274

258275
fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError> {
259276
let abs_path = self.abs_path.join(name);
260277
let rel_path = self.rel_path.join(name);
261-
let module_path = Self::find_module(&abs_path)?;
278+
let resolved_path = Self::resolve_module(&abs_path)?;
262279

263280
self.abs_path = abs_path;
264281
self.rel_path = rel_path;
265-
self.module_path = module_path;
282+
self.resolved_path = resolved_path;
266283

267284
Ok(())
268285
}
269286

270287
fn has_module(&self) -> bool {
271-
self.module_path.is_file()
288+
(self.resolved_path.as_deref())
289+
.map(Path::is_file)
290+
.unwrap_or(false)
272291
}
273292

274293
fn cache_key(&self) -> String {
275-
self.module_path.display().to_string()
294+
self.resolved_path.as_deref().unwrap().display().to_string()
276295
}
277296

278297
fn has_config(&self) -> bool {
@@ -285,7 +304,9 @@ impl Require for TextRequirer {
285304

286305
fn loader(&self, lua: &Lua) -> Result<Function> {
287306
let name = format!("@{}", self.rel_path.display());
288-
lua.load(&*self.module_path).set_name(name).into_function()
307+
lua.load(self.resolved_path.as_deref().unwrap())
308+
.set_name(name)
309+
.into_function()
289310
}
290311
}
291312

@@ -496,7 +517,7 @@ unsafe fn write_to_buffer(
496517
}
497518

498519
#[cfg(feature = "luau")]
499-
pub fn create_require_function<R: Require + 'static>(lua: &Lua, require: R) -> Result<Function> {
520+
pub(super) fn create_require_function<R: Require + 'static>(lua: &Lua, require: R) -> Result<Function> {
500521
unsafe extern "C-unwind" fn find_current_file(state: *mut ffi::lua_State) -> c_int {
501522
let mut ar: ffi::lua_Debug = mem::zeroed();
502523
for level in 2.. {

src/prelude.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ pub use crate::HookTriggers as LuaHookTriggers;
2525
#[doc(no_inline)]
2626
pub use crate::{
2727
CompileConstant as LuaCompileConstant, CoverageInfo as LuaCoverageInfo,
28-
NavigateError as LuaNavigateError, Require as LuaRequire, Vector as LuaVector,
28+
NavigateError as LuaNavigateError, Require as LuaRequire, TextRequirer as LuaTextRequirer,
29+
Vector as LuaVector,
2930
};
3031

3132
#[cfg(feature = "async")]

0 commit comments

Comments
 (0)