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(&self) -> (full_moon::LuaVersion, Vec<lua_versions::LuaVersionError>) {
355 let mut errors = Vec::new();
356
357 let mut lua_version = full_moon::LuaVersion::lua51();
358
359 for version in &self.lua_versions {
360 match version.to_lua_version() {
361 Ok(version) => lua_version |= version,
362 Err(error) => errors.push(error),
363 }
364 }
365
366 (lua_version, errors)
367 }
368}
369
370macro_rules! names {
371 {$($name:expr => $path:expr,)+} => {
372 impl StandardLibrary {
373 pub fn from_name(name: &str) -> Option<StandardLibrary> {
374 match name {
375 $(
376 $name => {
377 StandardLibrary::from_builtin_name(name, include_str!($path))
378 },
379 )+
380
381 _ => None
382 }
383 }
384
385 pub fn all_default_standard_libraries() -> &'static HashMap<&'static str, StandardLibrary> {
386 static CACHED_RESULT: OnceCell<HashMap<&'static str, StandardLibrary>> = OnceCell::new();
387
388 CACHED_RESULT.get_or_init(|| {
389 let mut stds = HashMap::new();
390
391 $(
392 stds.insert(
393 $name,
394 StandardLibrary::from_name($name).unwrap(),
395 );
396 )+
397
398 stds
399 })
400 }
401 }
402 };
403}
404
405names! {
406 "lua51" => "../../default_std/lua51.yml",
407 "lua52" => "../../default_std/lua52.yml",
408 "lua53" => "../../default_std/lua53.yml",
409 "luau" => "../../default_std/luau.yml",
410}
411
412fn is_default<T>(value: &T) -> bool
413where
414 T: Default + PartialEq<T>,
415{
416 value == &T::default()
417}
418
419#[derive(Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
420pub struct FunctionBehavior {
421 #[serde(rename = "args")]
422 pub arguments: Vec<Argument>,
423
424 #[serde(default)]
425 #[serde(skip_serializing_if = "is_false")]
426 pub method: bool,
427
428 #[serde(default)]
429 #[serde(skip_serializing_if = "is_false")]
430 pub must_use: bool,
431}
432
433fn is_false(value: &bool) -> bool {
434 !value
435}
436
437#[derive(Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
438pub struct Field {
439 #[serde(flatten)]
440 pub field_kind: FieldKind,
441
442 #[serde(default)]
443 #[serde(skip_serializing_if = "Option::is_none")]
444 pub deprecated: Option<Deprecated>,
445}
446
447impl Field {
448 pub const fn from_field_kind(field_kind: FieldKind) -> Self {
449 Self {
450 field_kind,
451 deprecated: None,
452 }
453 }
454
455 pub fn with_deprecated(self, deprecated: Option<Deprecated>) -> Self {
456 Self { deprecated, ..self }
457 }
458}
459
460#[derive(Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
461#[serde(rename_all = "kebab-case")]
462pub struct Deprecated {
463 pub message: String,
464
465 #[serde(default)]
470 pub replace: Vec<String>,
471}
472
473impl Deprecated {
474 fn regex_pattern() -> Regex {
475 Regex::new(r"%(%|(?P<number>[0-9]+)|(\.\.\.))").unwrap()
476 }
477
478 pub fn try_instead(&self, parameters: &[String]) -> Option<String> {
479 profiling::scope!("Deprecated::try_instead");
480
481 let regex_pattern = Deprecated::regex_pattern();
482
483 for replace_format in &self.replace {
484 let mut success = true;
485
486 let new_message = regex_pattern.replace_all(replace_format, |captures: &Captures| {
487 if let Some(number) = captures.name("number") {
488 let number = match number.as_str().parse::<u32>() {
489 Ok(number) => number,
490 Err(_) => {
491 success = false;
492 return Cow::Borrowed("");
493 }
494 };
495
496 if number > parameters.len() as u32 || number == 0 {
497 success = false;
498 return Cow::Borrowed("");
499 }
500
501 return Cow::Borrowed(¶meters[number as usize - 1]);
502 }
503
504 let capture = captures.get(1).unwrap();
505 match capture.as_str() {
506 "%" => Cow::Borrowed("%"),
507 "..." => Cow::Owned(parameters.join(", ")),
508 other => unreachable!("Unexpected capture in deprecated formatting: {}", other),
509 }
510 });
511
512 if !success {
513 continue;
514 }
515
516 return Some(new_message.into_owned());
517 }
518
519 None
520 }
521}
522
523#[derive(Clone, Debug, Hash, PartialEq, Eq)]
524pub enum FieldKind {
525 Any,
526 Function(FunctionBehavior),
527 Property(PropertyWritability),
528 Struct(String),
529 Removed,
530}
531
532#[derive(Clone, Copy, Debug, PartialEq, Eq)]
533struct TrueOnly;
534
535impl<'de> Deserialize<'de> for TrueOnly {
536 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
537 where
538 D: Deserializer<'de>,
539 {
540 match bool::deserialize(deserializer) {
541 Ok(true) => Ok(TrueOnly),
542 _ => Err(de::Error::custom("expected `true`")),
543 }
544 }
545}
546
547impl Serialize for TrueOnly {
548 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
549 where
550 S: Serializer,
551 {
552 serializer.serialize_bool(true)
553 }
554}
555
556#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
557#[serde(untagged)]
558enum FieldKindSerde {
559 Any { any: TrueOnly },
560 Function(FunctionBehavior),
561 Removed { removed: TrueOnly },
562 Property { property: PropertyWritability },
563 Struct { r#struct: String },
564}
565
566impl<'de> Deserialize<'de> for FieldKind {
567 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
568 where
569 D: Deserializer<'de>,
570 {
571 let field_kind = FieldKindSerde::deserialize(deserializer)?;
572
573 Ok(match field_kind {
574 FieldKindSerde::Any { .. } => FieldKind::Any,
575 FieldKindSerde::Function(function_behavior) => FieldKind::Function(function_behavior),
576 FieldKindSerde::Removed { .. } => FieldKind::Removed,
577 FieldKindSerde::Property { property } => FieldKind::Property(property),
578 FieldKindSerde::Struct { r#struct } => FieldKind::Struct(r#struct),
579 })
580 }
581}
582
583impl Serialize for FieldKind {
584 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
585 where
586 S: Serializer,
587 {
588 let field_kind = match self {
589 FieldKind::Any => FieldKindSerde::Any { any: TrueOnly },
590 FieldKind::Function(function_behavior) => {
591 FieldKindSerde::Function(function_behavior.to_owned())
592 }
593 FieldKind::Removed => FieldKindSerde::Removed { removed: TrueOnly },
594 FieldKind::Property(property_writability) => FieldKindSerde::Property {
595 property: *property_writability,
596 },
597
598 FieldKind::Struct(r#struct) => FieldKindSerde::Struct {
599 r#struct: r#struct.to_owned(),
600 },
601 };
602
603 field_kind.serialize(serializer)
604 }
605}
606
607#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
608#[serde(rename_all = "kebab-case")]
609pub enum PropertyWritability {
610 ReadOnly,
612 NewFields,
614 OverrideFields,
616 FullWrite,
618}
619
620#[derive(Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
621pub struct Argument {
622 #[serde(default)]
623 #[serde(skip_serializing_if = "is_default")]
624 pub required: Required,
625
626 #[serde(rename = "type")]
627 pub argument_type: ArgumentType,
628
629 #[serde(default)]
630 #[serde(skip_serializing_if = "is_default")]
631 pub observes: Observes,
632
633 #[serde(default)]
634 #[serde(skip_serializing_if = "Option::is_none")]
635 pub deprecated: Option<Deprecated>,
636}
637
638#[derive(Clone, Debug, Hash, PartialEq, Eq)]
639pub enum ArgumentType {
641 Any,
642 Bool,
643 Constant(Vec<String>),
644 Display(String),
645 Function,
647 Nil,
648 Number,
649 String,
650 Table,
652 Vararg,
654}
655
656impl Serialize for ArgumentType {
657 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
658 match self {
659 &ArgumentType::Any
660 | &ArgumentType::Bool
661 | &ArgumentType::Function
662 | &ArgumentType::Nil
663 | &ArgumentType::Number
664 | &ArgumentType::String
665 | &ArgumentType::Table
666 | &ArgumentType::Vararg => serializer.serialize_str(&self.to_string()),
667
668 ArgumentType::Constant(constants) => {
669 let mut seq = serializer.serialize_seq(Some(constants.len()))?;
670 for constant in constants {
671 seq.serialize_element(constant)?;
672 }
673 seq.end()
674 }
675
676 ArgumentType::Display(display) => {
677 let mut map = serializer.serialize_map(Some(1))?;
678 map.serialize_entry("display", display)?;
679 map.end()
680 }
681 }
682 }
683}
684
685impl<'de> Deserialize<'de> for ArgumentType {
686 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
687 deserializer.deserialize_any(ArgumentTypeVisitor)
688 }
689}
690
691struct ArgumentTypeVisitor;
692
693impl<'de> Visitor<'de> for ArgumentTypeVisitor {
694 type Value = ArgumentType;
695
696 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
697 formatter.write_str("an argument type or an array of constant strings")
698 }
699
700 fn visit_map<A: de::MapAccess<'de>>(self, mut access: A) -> Result<Self::Value, A::Error> {
701 let mut map: HashMap<String, String> = HashMap::new();
702
703 while let Some((key, value)) = access.next_entry()? {
704 map.insert(key, value);
705 }
706
707 if let Some(display) = map.remove("display") {
708 Ok(ArgumentType::Display(display))
709 } else {
710 Err(de::Error::custom(
711 "map value must have a `display` property",
712 ))
713 }
714 }
715
716 fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
717 let mut constants = Vec::new();
718
719 while let Some(value) = seq.next_element()? {
720 constants.push(value);
721 }
722
723 Ok(ArgumentType::Constant(constants))
724 }
725
726 fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
727 match value {
728 "any" => Ok(ArgumentType::Any),
729 "bool" => Ok(ArgumentType::Bool),
730 "function" => Ok(ArgumentType::Function),
731 "nil" => Ok(ArgumentType::Nil),
732 "number" => Ok(ArgumentType::Number),
733 "string" => Ok(ArgumentType::String),
734 "table" => Ok(ArgumentType::Table),
735 "..." => Ok(ArgumentType::Vararg),
736 other => Err(de::Error::custom(format!("unknown type {other}"))),
737 }
738 }
739}
740
741impl fmt::Display for ArgumentType {
742 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
743 match self {
744 ArgumentType::Any => write!(formatter, "any"),
745 ArgumentType::Bool => write!(formatter, "bool"),
746 ArgumentType::Constant(options) => write!(
747 formatter,
748 "{}",
749 options
751 .iter()
752 .map(|string| format!("\"{string}\""))
753 .collect::<Vec<_>>()
754 .join(", ")
755 ),
756 ArgumentType::Display(display) => write!(formatter, "{display}"),
757 ArgumentType::Function => write!(formatter, "function"),
758 ArgumentType::Nil => write!(formatter, "nil"),
759 ArgumentType::Number => write!(formatter, "number"),
760 ArgumentType::String => write!(formatter, "string"),
761 ArgumentType::Table => write!(formatter, "table"),
762 ArgumentType::Vararg => write!(formatter, "..."),
763 }
764 }
765}
766
767#[derive(Clone, Debug, Hash, PartialEq, Eq)]
768pub enum Required {
769 NotRequired,
770 Required(Option<String>),
771}
772
773impl Default for Required {
774 fn default() -> Self {
775 Required::Required(None)
776 }
777}
778
779impl<'de> Deserialize<'de> for Required {
780 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
781 deserializer.deserialize_any(RequiredVisitor)
782 }
783}
784
785impl Serialize for Required {
786 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
787 match self {
788 Required::NotRequired => serializer.serialize_bool(false),
789 Required::Required(None) => serializer.serialize_bool(true),
790 Required::Required(Some(message)) => serializer.serialize_str(message),
791 }
792 }
793}
794
795struct RequiredVisitor;
796
797impl Visitor<'_> for RequiredVisitor {
798 type Value = Required;
799
800 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
801 formatter.write_str("a boolean or a string message (when required)")
802 }
803
804 fn visit_bool<E: de::Error>(self, value: bool) -> Result<Self::Value, E> {
805 if value {
806 Ok(Required::Required(None))
807 } else {
808 Ok(Required::NotRequired)
809 }
810 }
811
812 fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
813 Ok(Required::Required(Some(value.to_owned())))
814 }
815}
816
817#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
818#[serde(rename_all = "kebab-case")]
819pub enum Observes {
820 ReadWrite,
821 Read,
822 Write,
823}
824
825impl Default for Observes {
826 fn default() -> Self {
827 Self::ReadWrite
828 }
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}