darklua_core/rules/require/
path_require_mode.rs

1use serde::{Deserialize, Serialize};
2
3use crate::frontend::DarkluaResult;
4use crate::nodes::FunctionCall;
5use crate::rules::require::match_path_require_call;
6use crate::rules::Context;
7use crate::DarkluaError;
8
9use std::collections::HashMap;
10use std::ffi::OsStr;
11use std::path::{Path, PathBuf};
12
13use super::RequirePathLocator;
14
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
16#[serde(deny_unknown_fields, rename_all = "snake_case")]
17pub struct PathRequireMode {
18    #[serde(
19        skip_serializing_if = "is_default_module_folder_name",
20        default = "get_default_module_folder_name"
21    )]
22    module_folder_name: String,
23    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
24    sources: HashMap<String, PathBuf>,
25}
26
27impl Default for PathRequireMode {
28    fn default() -> Self {
29        Self {
30            module_folder_name: get_default_module_folder_name(),
31            sources: Default::default(),
32        }
33    }
34}
35
36const DEFAULT_MODULE_FOLDER_NAME: &str = "init";
37
38#[inline]
39fn get_default_module_folder_name() -> String {
40    DEFAULT_MODULE_FOLDER_NAME.to_owned()
41}
42
43fn is_default_module_folder_name(value: &String) -> bool {
44    value == DEFAULT_MODULE_FOLDER_NAME
45}
46
47impl PathRequireMode {
48    pub fn new(module_folder_name: impl Into<String>) -> Self {
49        Self {
50            module_folder_name: module_folder_name.into(),
51            sources: Default::default(),
52        }
53    }
54
55    pub(crate) fn module_folder_name(&self) -> &str {
56        &self.module_folder_name
57    }
58
59    pub(crate) fn get_source(&self, name: &str) -> Option<&Path> {
60        self.sources.get(name).map(PathBuf::as_path)
61    }
62
63    pub(crate) fn find_require(
64        &self,
65        call: &FunctionCall,
66        context: &Context,
67    ) -> DarkluaResult<Option<PathBuf>> {
68        if let Some(literal_path) = match_path_require_call(call) {
69            let required_path =
70                RequirePathLocator::new(self, context.project_location(), context.resources())
71                    .find_require_path(literal_path, context.current_path())?;
72
73            Ok(Some(required_path))
74        } else {
75            Ok(None)
76        }
77    }
78
79    pub(crate) fn is_module_folder_name(&self, path: &Path) -> bool {
80        let expect_value = Some(self.module_folder_name.as_str());
81        path.file_name().and_then(OsStr::to_str) == expect_value
82            || path.file_stem().and_then(OsStr::to_str) == expect_value
83    }
84
85    pub(crate) fn generate_require(
86        &self,
87        _path: &Path,
88        _current_mode: &crate::rules::RequireMode,
89        _context: &Context<'_, '_, '_>,
90    ) -> Result<Option<crate::nodes::Arguments>, crate::DarkluaError> {
91        Err(DarkluaError::custom("unsupported target require mode")
92            .context("path require mode cannot"))
93    }
94}
95
96#[cfg(test)]
97mod test {
98    use super::*;
99
100    mod is_module_folder_name {
101        use super::*;
102
103        #[test]
104        fn default_mode_is_false_for_regular_name() {
105            let require_mode = PathRequireMode::default();
106
107            assert!(!require_mode.is_module_folder_name(Path::new("oops.lua")));
108        }
109
110        #[test]
111        fn default_mode_is_true_for_init_lua() {
112            let require_mode = PathRequireMode::default();
113
114            assert!(require_mode.is_module_folder_name(Path::new("init.lua")));
115        }
116
117        #[test]
118        fn default_mode_is_true_for_init_luau() {
119            let require_mode = PathRequireMode::default();
120
121            assert!(require_mode.is_module_folder_name(Path::new("init.luau")));
122        }
123
124        #[test]
125        fn default_mode_is_true_for_folder_init_lua() {
126            let require_mode = PathRequireMode::default();
127
128            assert!(require_mode.is_module_folder_name(Path::new("folder/init.lua")));
129        }
130
131        #[test]
132        fn default_mode_is_true_for_folder_init_luau() {
133            let require_mode = PathRequireMode::default();
134
135            assert!(require_mode.is_module_folder_name(Path::new("folder/init.luau")));
136        }
137    }
138}