1mod lua_versions;
2pub mod v1;
3mod v1_upgrade;
4
5use std::{
6 borrow::{Borrow, Cow},
7 collections::{BTreeMap, HashMap},
8 fmt, io,
9};
10
11use once_cell::sync::OnceCell;
12use regex::{Captures, Regex};
13use serde::{
14 de::{self, Deserializer, Visitor},
15 ser::{SerializeMap, SerializeSeq, Serializer},
16 Deserialize, Serialize,
17};
18
19pub use lua_versions::*;
20
21lazy_static::lazy_static! {
22 static ref ANY_TABLE: BTreeMap<String, Field> = {
23 let mut map = BTreeMap::new();
24 map.insert("*".to_owned(), Field::from_field_kind(FieldKind::Any));
25 map
26 };
27}
28
29#[derive(Clone, Debug, PartialEq, Eq)]
30enum GlobalTreeField {
31 Key(String),
32 ReadOnlyField,
33}
34
35#[derive(Clone, Debug, PartialEq, Eq)]
36struct GlobalTreeNode {
37 children: BTreeMap<String, GlobalTreeNode>,
38 field: GlobalTreeField,
39}
40
41impl GlobalTreeNode {
42 fn field<'a>(&self, names_to_fields: &'a BTreeMap<String, Field>) -> &'a Field {
43 static READ_ONLY_FIELD: Field =
44 Field::from_field_kind(FieldKind::Property(PropertyWritability::ReadOnly));
45
46 match &self.field {
47 GlobalTreeField::Key(key) => names_to_fields
48 .get(key)
49 .unwrap_or_else(|| panic!("couldn't find {key} inside names_to_fields")),
50
51 GlobalTreeField::ReadOnlyField => &READ_ONLY_FIELD,
52 }
53 }
54}
55
56#[derive(Clone, Debug, Default, PartialEq, Eq)]
57struct GlobalTreeCache {
58 cache: BTreeMap<String, GlobalTreeNode>,
59
60 #[cfg(debug_assertions)]
61 last_globals_hash: u64,
62}
63
64#[profiling::function]
65fn extract_into_tree(
66 names_to_fields: &BTreeMap<String, Field>,
67) -> BTreeMap<String, GlobalTreeNode> {
68 let mut fields: BTreeMap<String, GlobalTreeNode> = BTreeMap::new();
69
70 for name in names_to_fields.keys() {
71 let mut current = &mut fields;
72
73 let mut split = name.split('.').collect::<Vec<_>>();
74 let final_name = split.pop().unwrap();
75
76 for segment in split {
77 current = &mut current
78 .entry(segment.to_string())
79 .or_insert_with(|| GlobalTreeNode {
80 field: GlobalTreeField::ReadOnlyField,
81 children: BTreeMap::new(),
82 })
83 .children;
84 }
85
86 let tree_field_key = GlobalTreeField::Key(name.to_owned());
87
88 if let Some(existing_segment) = current.get_mut(final_name) {
89 existing_segment.field = tree_field_key;
90 } else {
91 current.insert(
92 final_name.to_string(),
93 GlobalTreeNode {
94 field: tree_field_key,
95 children: BTreeMap::new(),
96 },
97 );
98 }
99 }
100
101 fields
102}
103
104#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
105#[serde(deny_unknown_fields)]
106pub struct StandardLibrary {
107 #[serde(default)]
108 #[serde(skip_serializing_if = "Option::is_none")]
109 pub base: Option<String>,
110
111 #[serde(default)]
112 #[serde(skip_serializing_if = "Option::is_none")]
113 pub name: Option<String>,
114
115 #[serde(default)]
116 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
117 pub globals: BTreeMap<String, Field>,
118
119 #[serde(default)]
120 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
121 pub structs: BTreeMap<String, BTreeMap<String, Field>>,
122
123 #[serde(default)]
124 pub lua_versions: Vec<lua_versions::LuaVersion>,
125
126 #[serde(default)]
128 #[serde(skip_serializing_if = "Option::is_none")]
129 pub last_updated: Option<i64>,
130
131 #[serde(default)]
132 #[serde(skip_serializing_if = "Option::is_none")]
133 pub last_selene_version: Option<String>,
134
135 #[serde(default)]
136 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
137 pub roblox_classes: BTreeMap<String, RobloxClass>,
138
139 #[serde(skip)]
140 global_tree_cache: OnceCell<GlobalTreeCache>,
141}
142
143#[derive(Debug)]
144pub enum StandardLibraryError {
145 DeserializeTomlError(toml::de::Error),
146 DeserializeYamlError(serde_yaml::Error),
147 IoError(io::Error),
148}
149
150impl fmt::Display for StandardLibraryError {
151 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
152 match self {
153 StandardLibraryError::DeserializeTomlError(error) => {
154 write!(formatter, "deserialize toml error: {error}")
155 }
156
157 StandardLibraryError::DeserializeYamlError(error) => {
158 write!(formatter, "deserialize yaml error: {error}")
159 }
160
161 StandardLibraryError::IoError(error) => write!(formatter, "io error: {error}"),
162 }
163 }
164}
165
166impl std::error::Error for StandardLibraryError {
167 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
168 use StandardLibraryError::*;
169
170 match self {
171 DeserializeTomlError(error) => Some(error),
172 DeserializeYamlError(error) => Some(error),
173 IoError(error) => Some(error),
174 }
175 }
176}
177
178impl From<io::Error> for StandardLibraryError {
179 fn from(error: io::Error) -> Self {
180 StandardLibraryError::IoError(error)
181 }
182}
183
184impl StandardLibrary {
185 fn global_tree_cache(&self) -> &BTreeMap<String, GlobalTreeNode> {
187 #[cfg(debug_assertions)]
189 let hash = {
190 use std::{
191 collections::hash_map::DefaultHasher,
192 hash::{Hash, Hasher},
193 };
194
195 profiling::scope!("global_tree_cache: hash");
196
197 let mut hasher = DefaultHasher::new();
198 self.globals.hash(&mut hasher);
199 hasher.finish()
200 };
201
202 if let Some(cache) = self.global_tree_cache.get() {
203 profiling::scope!("global_tree_cache: cache hit");
204
205 #[cfg(debug_assertions)]
206 assert_eq!(
207 cache.last_globals_hash, hash,
208 "globals changed after global_tree_cache has already been created"
209 );
210
211 return &cache.cache;
212 }
213
214 profiling::scope!("global_tree_cache: cache not set");
215
216 &self
217 .global_tree_cache
218 .get_or_init(|| {
219 profiling::scope!("global_tree_cache: create cache");
220 GlobalTreeCache {
221 cache: extract_into_tree(&self.globals),
222
223 #[cfg(debug_assertions)]
224 last_globals_hash: hash,
225 }
226 })
227 .cache
228 }
229
230 #[profiling::function]
239 pub fn find_global<S: Borrow<str>>(&self, names: &[S]) -> Option<&Field> {
240 assert!(!names.is_empty());
241
242 if let Some(explicit_global) = self.globals.get(&names.join(".")) {
243 profiling::scope!("find_global: explicit global");
244 return Some(explicit_global);
245 }
246
247 let mut last_extracted_struct;
249
250 let mut current = self.global_tree_cache();
251 let mut current_names_to_fields = &self.globals;
252
253 profiling::scope!("find_global: look through global tree cache");
254
255 for name in names.iter().take(names.len() - 1) {
256 let found_segment = current.get(name.borrow()).or_else(|| current.get("*"))?;
257 let field = found_segment.field(current_names_to_fields);
258
259 match &field.field_kind {
260 FieldKind::Any => {
261 return Some(field);
262 }
263
264 FieldKind::Struct(struct_name) => {
265 let strukt = self
266 .structs
267 .get(struct_name)
268 .unwrap_or_else(|| panic!("struct `{struct_name}` not found"));
269
270 last_extracted_struct = extract_into_tree(strukt);
271 current_names_to_fields = strukt;
272 current = &last_extracted_struct;
273 }
274
275 _ => {
276 current = &found_segment.children;
277 }
278 }
279 }
280
281 current
282 .get(names.last().unwrap().borrow())
283 .or_else(|| current.get("*"))
284 .map(|node| node.field(current_names_to_fields))
285 }
286
287 pub fn global_has_fields(&self, name: &str) -> bool {
288 profiling::scope!("global_has_fields", name);
289 self.global_tree_cache().contains_key(name)
290 }
291
292 pub fn extend(&mut self, other: StandardLibrary) {
293 self.structs.extend(other.structs);
294
295 let mut globals: BTreeMap<String, Field> = other
297 .globals
298 .into_iter()
299 .filter(|(other_field_name, other_field)| {
300 other_field.field_kind != FieldKind::Removed
301 && !matches!(
302 self.globals.get(other_field_name),
303 Some(Field {
304 field_kind: FieldKind::Removed,
305 ..
306 })
307 )
308 })
309 .collect();
310
311 globals.extend(
312 std::mem::take(&mut self.globals)
313 .into_iter()
314 .filter_map(|(key, value)| {
315 if value.field_kind == FieldKind::Removed {
316 None
317 } else {
318 Some((key, value))
319 }
320 }),
321 );
322
323 if !other.lua_versions.is_empty() {
325 self.lua_versions = other.lua_versions;
326 }
327
328 self.globals = globals;
329 }
330
331 #[cfg(feature = "roblox")]
332 pub fn roblox_base() -> StandardLibrary {
333 StandardLibrary::from_builtin_name(
334 "roblox",
335 include_str!("../../default_std/roblox_base.yml"),
336 )
337 .expect("roblox_base.yml is missing")
338 }
339
340 fn from_builtin_name(name: &str, contents: &str) -> Option<StandardLibrary> {
341 let mut std = serde_yaml::from_str::<StandardLibrary>(contents).unwrap_or_else(|error| {
342 panic!("default standard library '{name}' failed deserialization: {error}")
343 });
344
345 if let Some(base_name) = &std.base {
346 let base = StandardLibrary::from_name(base_name);
347
348 std.extend(base.expect("built-in library based off of non-existent built-in"));
349 }
350
351 Some(std)
352 }
353
354 pub fn lua_version(
355 &self,
356 ) -> (
357 full_moon::LuaVersion,
358 Vec<lua_versions::LuaVersionError<'_>>,
359 ) {
360 let mut errors = Vec::new();
361
362 let mut lua_version = full_moon::LuaVersion::lua51();
363
364 for version in &self.lua_versions {
365 match version.to_lua_version() {
366 Ok(version) => lua_version |= version,
367 Err(error) => errors.push(error),
368 }
369 }
370
371 (lua_version, errors)
372 }
373}
374
375macro_rules! names {
376 {$($name:expr => $path:expr,)+} => {
377 impl StandardLibrary {
378 pub fn from_name(name: &str) -> Option<StandardLibrary> {
379 match name {
380 $(
381 $name => {
382 StandardLibrary::from_builtin_name(name, include_str!($path))
383 },
384 )+
385
386 _ => None
387 }
388 }
389
390 pub fn all_default_standard_libraries() -> &'static HashMap<&'static str, StandardLibrary> {
391 static CACHED_RESULT: OnceCell<HashMap<&'static str, StandardLibrary>> = OnceCell::new();
392
393 CACHED_RESULT.get_or_init(|| {
394 let mut stds = HashMap::new();
395
396 $(
397 stds.insert(
398 $name,
399 StandardLibrary::from_name($name).unwrap(),
400 );
401 )+
402
403 stds
404 })
405 }
406 }
407 };
408}
409
410names! {
411 "lua51" => "../../default_std/lua51.yml",
412 "lua52" => "../../default_std/lua52.yml",
413 "lua53" => "../../default_std/lua53.yml",
414 "luau" => "../../default_std/luau.yml",
415}
416
417fn is_default<T>(value: &T) -> bool
418where
419 T: Default + PartialEq<T>,
420{
421 value == &T::default()
422}
423
424#[derive(Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
425pub struct FunctionBehavior {
426 #[serde(rename = "args")]
427 pub arguments: Vec<Argument>,
428
429 #[serde(default)]
430 #[serde(skip_serializing_if = "is_false")]
431 pub method: bool,
432
433 #[serde(default)]
434 #[serde(skip_serializing_if = "is_false")]
435 pub must_use: bool,
436}
437
438fn is_false(value: &bool) -> bool {
439 !value
440}
441
442#[derive(Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
443pub struct Field {
444 #[serde(flatten)]
445 pub field_kind: FieldKind,
446
447 #[serde(default)]
448 #[serde(skip_serializing_if = "Option::is_none")]
449 pub deprecated: Option<Deprecated>,
450}
451
452impl Field {
453 pub const fn from_field_kind(field_kind: FieldKind) -> Self {
454 Self {
455 field_kind,
456 deprecated: None,
457 }
458 }
459
460 pub fn with_deprecated(self, deprecated: Option<Deprecated>) -> Self {
461 Self { deprecated, ..self }
462 }
463}
464
465#[derive(Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
466#[serde(rename_all = "kebab-case")]
467pub struct Deprecated {
468 pub message: String,
469
470 #[serde(default)]
475 pub replace: Vec<String>,
476}
477
478impl Deprecated {
479 fn regex_pattern() -> Regex {
480 Regex::new(r"%(%|(?P<number>[0-9]+)|(\.\.\.))").unwrap()
481 }
482
483 pub fn try_instead(&self, parameters: &[String]) -> Option<String> {
484 profiling::scope!("Deprecated::try_instead");
485
486 let regex_pattern = Deprecated::regex_pattern();
487
488 for replace_format in &self.replace {
489 let mut success = true;
490
491 let new_message = regex_pattern.replace_all(replace_format, |captures: &Captures| {
492 if let Some(number) = captures.name("number") {
493 let number = match number.as_str().parse::<u32>() {
494 Ok(number) => number,
495 Err(_) => {
496 success = false;
497 return Cow::Borrowed("");
498 }
499 };
500
501 if number > parameters.len() as u32 || number == 0 {
502 success = false;
503 return Cow::Borrowed("");
504 }
505
506 return Cow::Borrowed(¶meters[number as usize - 1]);
507 }
508
509 let capture = captures.get(1).unwrap();
510 match capture.as_str() {
511 "%" => Cow::Borrowed("%"),
512 "..." => Cow::Owned(parameters.join(", ")),
513 other => unreachable!("Unexpected capture in deprecated formatting: {}", other),
514 }
515 });
516
517 if !success {
518 continue;
519 }
520
521 return Some(new_message.into_owned());
522 }
523
524 None
525 }
526}
527
528#[derive(Clone, Debug, Hash, PartialEq, Eq)]
529pub enum FieldKind {
530 Any,
531 Function(FunctionBehavior),
532 Property(PropertyWritability),
533 Struct(String),
534 Removed,
535}
536
537#[derive(Clone, Copy, Debug, PartialEq, Eq)]
538struct TrueOnly;
539
540impl<'de> Deserialize<'de> for TrueOnly {
541 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
542 where
543 D: Deserializer<'de>,
544 {
545 match bool::deserialize(deserializer) {
546 Ok(true) => Ok(TrueOnly),
547 _ => Err(de::Error::custom("expected `true`")),
548 }
549 }
550}
551
552impl Serialize for TrueOnly {
553 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
554 where
555 S: Serializer,
556 {
557 serializer.serialize_bool(true)
558 }
559}
560
561#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
562#[serde(untagged)]
563enum FieldKindSerde {
564 Any { any: TrueOnly },
565 Function(FunctionBehavior),
566 Removed { removed: TrueOnly },
567 Property { property: PropertyWritability },
568 Struct { r#struct: String },
569}
570
571impl<'de> Deserialize<'de> for FieldKind {
572 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
573 where
574 D: Deserializer<'de>,
575 {
576 let field_kind = FieldKindSerde::deserialize(deserializer)?;
577
578 Ok(match field_kind {
579 FieldKindSerde::Any { .. } => FieldKind::Any,
580 FieldKindSerde::Function(function_behavior) => FieldKind::Function(function_behavior),
581 FieldKindSerde::Removed { .. } => FieldKind::Removed,
582 FieldKindSerde::Property { property } => FieldKind::Property(property),
583 FieldKindSerde::Struct { r#struct } => FieldKind::Struct(r#struct),
584 })
585 }
586}
587
588impl Serialize for FieldKind {
589 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
590 where
591 S: Serializer,
592 {
593 let field_kind = match self {
594 FieldKind::Any => FieldKindSerde::Any { any: TrueOnly },
595 FieldKind::Function(function_behavior) => {
596 FieldKindSerde::Function(function_behavior.to_owned())
597 }
598 FieldKind::Removed => FieldKindSerde::Removed { removed: TrueOnly },
599 FieldKind::Property(property_writability) => FieldKindSerde::Property {
600 property: *property_writability,
601 },
602
603 FieldKind::Struct(r#struct) => FieldKindSerde::Struct {
604 r#struct: r#struct.to_owned(),
605 },
606 };
607
608 field_kind.serialize(serializer)
609 }
610}
611
612#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
613#[serde(rename_all = "kebab-case")]
614pub enum PropertyWritability {
615 ReadOnly,
617 NewFields,
619 OverrideFields,
621 FullWrite,
623}
624
625#[derive(Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
626pub struct Argument {
627 #[serde(default)]
628 #[serde(skip_serializing_if = "is_default")]
629 pub required: Required,
630
631 #[serde(rename = "type")]
632 pub argument_type: ArgumentType,
633
634 #[serde(default)]
635 #[serde(skip_serializing_if = "is_default")]
636 pub observes: Observes,
637
638 #[serde(default)]
639 #[serde(skip_serializing_if = "Option::is_none")]
640 pub deprecated: Option<Deprecated>,
641}
642
643#[derive(Clone, Debug, Hash, PartialEq, Eq)]
644pub enum ArgumentType {
646 Any,
647 Bool,
648 Constant(Vec<String>),
649 Display(String),
650 Function,
652 Nil,
653 Number,
654 String,
655 Table,
657 Vararg,
659}
660
661impl Serialize for ArgumentType {
662 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
663 match self {
664 &ArgumentType::Any
665 | &ArgumentType::Bool
666 | &ArgumentType::Function
667 | &ArgumentType::Nil
668 | &ArgumentType::Number
669 | &ArgumentType::String
670 | &ArgumentType::Table
671 | &ArgumentType::Vararg => serializer.serialize_str(&self.to_string()),
672
673 ArgumentType::Constant(constants) => {
674 let mut seq = serializer.serialize_seq(Some(constants.len()))?;
675 for constant in constants {
676 seq.serialize_element(constant)?;
677 }
678 seq.end()
679 }
680
681 ArgumentType::Display(display) => {
682 let mut map = serializer.serialize_map(Some(1))?;
683 map.serialize_entry("display", display)?;
684 map.end()
685 }
686 }
687 }
688}
689
690impl<'de> Deserialize<'de> for ArgumentType {
691 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
692 deserializer.deserialize_any(ArgumentTypeVisitor)
693 }
694}
695
696struct ArgumentTypeVisitor;
697
698impl<'de> Visitor<'de> for ArgumentTypeVisitor {
699 type Value = ArgumentType;
700
701 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
702 formatter.write_str("an argument type or an array of constant strings")
703 }
704
705 fn visit_map<A: de::MapAccess<'de>>(self, mut access: A) -> Result<Self::Value, A::Error> {
706 let mut map: HashMap<String, String> = HashMap::new();
707
708 while let Some((key, value)) = access.next_entry()? {
709 map.insert(key, value);
710 }
711
712 if let Some(display) = map.remove("display") {
713 Ok(ArgumentType::Display(display))
714 } else {
715 Err(de::Error::custom(
716 "map value must have a `display` property",
717 ))
718 }
719 }
720
721 fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
722 let mut constants = Vec::new();
723
724 while let Some(value) = seq.next_element()? {
725 constants.push(value);
726 }
727
728 Ok(ArgumentType::Constant(constants))
729 }
730
731 fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
732 match value {
733 "any" => Ok(ArgumentType::Any),
734 "bool" => Ok(ArgumentType::Bool),
735 "function" => Ok(ArgumentType::Function),
736 "nil" => Ok(ArgumentType::Nil),
737 "number" => Ok(ArgumentType::Number),
738 "string" => Ok(ArgumentType::String),
739 "table" => Ok(ArgumentType::Table),
740 "..." => Ok(ArgumentType::Vararg),
741 other => Err(de::Error::custom(format!("unknown type {other}"))),
742 }
743 }
744}
745
746impl fmt::Display for ArgumentType {
747 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
748 match self {
749 ArgumentType::Any => write!(formatter, "any"),
750 ArgumentType::Bool => write!(formatter, "bool"),
751 ArgumentType::Constant(options) => write!(
752 formatter,
753 "{}",
754 options
756 .iter()
757 .map(|string| format!("\"{string}\""))
758 .collect::<Vec<_>>()
759 .join(", ")
760 ),
761 ArgumentType::Display(display) => write!(formatter, "{display}"),
762 ArgumentType::Function => write!(formatter, "function"),
763 ArgumentType::Nil => write!(formatter, "nil"),
764 ArgumentType::Number => write!(formatter, "number"),
765 ArgumentType::String => write!(formatter, "string"),
766 ArgumentType::Table => write!(formatter, "table"),
767 ArgumentType::Vararg => write!(formatter, "..."),
768 }
769 }
770}
771
772#[derive(Clone, Debug, Hash, PartialEq, Eq)]
773pub enum Required {
774 NotRequired,
775 Required(Option<String>),
776}
777
778impl Default for Required {
779 fn default() -> Self {
780 Required::Required(None)
781 }
782}
783
784impl<'de> Deserialize<'de> for Required {
785 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
786 deserializer.deserialize_any(RequiredVisitor)
787 }
788}
789
790impl Serialize for Required {
791 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
792 match self {
793 Required::NotRequired => serializer.serialize_bool(false),
794 Required::Required(None) => serializer.serialize_bool(true),
795 Required::Required(Some(message)) => serializer.serialize_str(message),
796 }
797 }
798}
799
800struct RequiredVisitor;
801
802impl Visitor<'_> for RequiredVisitor {
803 type Value = Required;
804
805 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
806 formatter.write_str("a boolean or a string message (when required)")
807 }
808
809 fn visit_bool<E: de::Error>(self, value: bool) -> Result<Self::Value, E> {
810 if value {
811 Ok(Required::Required(None))
812 } else {
813 Ok(Required::NotRequired)
814 }
815 }
816
817 fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
818 Ok(Required::Required(Some(value.to_owned())))
819 }
820}
821
822#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, Deserialize, Serialize)]
823#[serde(rename_all = "kebab-case")]
824pub enum Observes {
825 #[default]
826 ReadWrite,
827 Read,
828 Write,
829}
830
831#[derive(Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
832pub struct RobloxClass {
833 pub superclass: String,
834 pub events: Vec<String>,
835 pub properties: Vec<String>,
836}
837
838impl RobloxClass {
839 pub fn has_event(&self, roblox_classes: &BTreeMap<String, RobloxClass>, event: &str) -> bool {
840 if self.events.iter().any(|other_event| other_event == event) {
841 true
842 } else if let Some(superclass) = roblox_classes.get(&self.superclass) {
843 superclass.has_event(roblox_classes, event)
844 } else {
845 false
846 }
847 }
848
849 pub fn has_property(
850 &self,
851 roblox_classes: &BTreeMap<String, RobloxClass>,
852 property: &str,
853 ) -> bool {
854 if self
855 .properties
856 .iter()
857 .any(|other_property| other_property == property)
858 {
859 true
860 } else if let Some(superclass) = roblox_classes.get(&self.superclass) {
861 superclass.has_property(roblox_classes, property)
862 } else {
863 false
864 }
865 }
866}
867
868#[cfg(test)]
869mod tests {
870 use super::*;
871
872 fn string_vec(strings: Vec<&str>) -> Vec<String> {
873 strings.into_iter().map(ToOwned::to_owned).collect()
874 }
875
876 #[test]
877 fn valid_serde() {
878 StandardLibrary::from_name("lua51").expect("lua51.toml wasn't found");
879 StandardLibrary::from_name("lua52").expect("lua52.toml wasn't found");
880 }
881
882 #[test]
883 fn deprecated_try_instead() {
884 let deprecated = Deprecated {
885 message: "You shouldn't see this".to_owned(),
886 replace: vec![
887 "eleven(%11)".to_owned(),
888 "four(%1, %2, %3, %4)".to_owned(),
889 "three(%1, %2, %3 %%3)".to_owned(),
890 "two(%1, %2)".to_owned(),
891 "one(%1)".to_owned(),
892 ],
893 };
894
895 assert_eq!(
896 deprecated.try_instead(&string_vec(vec!["a", "b", "c"])),
897 Some("three(a, b, c %3)".to_owned())
898 );
899
900 assert_eq!(
901 deprecated.try_instead(&string_vec(vec!["a", "b"])),
902 Some("two(a, b)".to_owned())
903 );
904
905 assert_eq!(
906 deprecated.try_instead(&string_vec(vec!["a"])),
907 Some("one(a)".to_owned())
908 );
909
910 assert_eq!(
911 deprecated.try_instead(&string_vec(vec![
912 "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11",
913 ])),
914 Some("eleven(11)".to_owned())
915 );
916
917 assert_eq!(deprecated.try_instead(&string_vec(vec![])), None);
918 }
919
920 #[test]
921 fn deprecated_varargs() {
922 let deprecated = Deprecated {
923 message: "You shouldn't see this".to_owned(),
924 replace: vec!["print(%...)".to_owned()],
925 };
926
927 assert_eq!(
928 deprecated.try_instead(&string_vec(vec!["a", "b", "c"])),
929 Some("print(a, b, c)".to_owned())
930 );
931 }
932}