use crate::{LuaType, LuaTypeDeclId};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LuaBuiltinAttributeKind {
Deprecated,
LspOptimization,
IndexAlias,
Constructor,
FieldAccessor,
}
impl LuaBuiltinAttributeKind {
pub const fn as_str(self) -> &'static str {
match self {
Self::Deprecated => "deprecated",
Self::LspOptimization => "lsp_optimization",
Self::IndexAlias => "index_alias",
Self::Constructor => "constructor",
Self::FieldAccessor => "field_accessor",
}
}
pub fn from_name(name: &str) -> Option<Self> {
match name {
"deprecated" => Some(Self::Deprecated),
"lsp_optimization" => Some(Self::LspOptimization),
"index_alias" => Some(Self::IndexAlias),
"constructor" => Some(Self::Constructor),
"field_accessor" => Some(Self::FieldAccessor),
_ => None,
}
}
}
pub trait LuaAttributeCollectionExt {
fn find_attribute_use(&self, id: &str) -> Option<&LuaAttributeUse>;
fn find_builtin_attribute(&self, kind: LuaBuiltinAttributeKind) -> Option<&LuaAttributeUse> {
self.find_attribute_use(kind.as_str())
}
}
impl LuaAttributeCollectionExt for [LuaAttributeUse] {
fn find_attribute_use(&self, id: &str) -> Option<&LuaAttributeUse> {
self.iter()
.find(|attribute_use| attribute_use.id_name() == id)
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct LuaAttributeUse {
pub id: LuaTypeDeclId,
pub args: Vec<(String, Option<LuaType>)>,
}
impl LuaAttributeUse {
pub fn new(id: LuaTypeDeclId, args: Vec<(String, Option<LuaType>)>) -> Self {
Self { id, args }
}
pub fn id_name(&self) -> &str {
self.id.get_name()
}
pub fn get_param_by_name(&self, name: &str) -> Option<&LuaType> {
self.args
.iter()
.find(|(n, _)| n == name)
.and_then(|(_, typ)| typ.as_ref())
}
pub fn builtin_kind(&self) -> Option<LuaBuiltinAttributeKind> {
LuaBuiltinAttributeKind::from_name(self.id_name())
}
pub fn is_builtin(&self, kind: LuaBuiltinAttributeKind) -> bool {
self.builtin_kind() == Some(kind)
}
pub fn get_string_param(&self, name: &str) -> Option<&str> {
match self.get_param_by_name(name) {
Some(LuaType::DocStringConst(value)) => Some(value.as_ref()),
_ => None,
}
}
pub fn get_bool_param(&self, name: &str) -> Option<bool> {
match self.get_param_by_name(name) {
Some(LuaType::DocBooleanConst(value)) => Some(*value),
_ => None,
}
}
pub fn as_deprecated(&self) -> Option<LuaDeprecatedAttribute<'_>> {
if !self.is_builtin(LuaBuiltinAttributeKind::Deprecated) {
return None;
}
Some(LuaDeprecatedAttribute {
message: self.get_string_param("message"),
})
}
pub fn as_lsp_optimization(&self) -> Option<LuaLspOptimizationAttribute> {
if !self.is_builtin(LuaBuiltinAttributeKind::LspOptimization) {
return None;
}
let code = match self.get_string_param("code")? {
"check_table_field" => LuaLspOptimizationCode::CheckTableField,
"delayed_definition" => LuaLspOptimizationCode::DelayedDefinition,
_ => return None,
};
Some(LuaLspOptimizationAttribute { code })
}
pub fn as_index_alias(&self) -> Option<LuaIndexAliasAttribute<'_>> {
if !self.is_builtin(LuaBuiltinAttributeKind::IndexAlias) {
return None;
}
Some(LuaIndexAliasAttribute {
name: self.get_string_param("name")?,
})
}
pub fn as_constructor(&self) -> Option<LuaConstructorAttribute<'_>> {
if !self.is_builtin(LuaBuiltinAttributeKind::Constructor) {
return None;
}
Some(LuaConstructorAttribute {
name: self.get_string_param("name")?,
root_class: self.get_string_param("root_class"),
strip_self: self.get_bool_param("strip_self").unwrap_or(true),
return_mode: match self.get_param_by_name("return_mode") {
Some(LuaType::DocStringConst(value)) => {
LuaConstructorReturnMode::from_name(value.as_ref())?
}
_ => LuaConstructorReturnMode::Default,
},
})
}
pub fn as_field_accessor(&self) -> Option<LuaFieldAccessorAttribute<'_>> {
if !self.is_builtin(LuaBuiltinAttributeKind::FieldAccessor) {
return None;
}
let convention = self
.get_string_param("convention")
.and_then(LuaFieldAccessorConvention::from_name)
.unwrap_or(LuaFieldAccessorConvention::CamelCase);
Some(LuaFieldAccessorAttribute {
convention,
getter: self.get_string_param("getter"),
setter: self.get_string_param("setter"),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct LuaDeprecatedAttribute<'a> {
pub message: Option<&'a str>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LuaLspOptimizationCode {
CheckTableField,
DelayedDefinition,
}
impl LuaLspOptimizationCode {
pub const fn as_str(self) -> &'static str {
match self {
Self::CheckTableField => "check_table_field",
Self::DelayedDefinition => "delayed_definition",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct LuaLspOptimizationAttribute {
pub code: LuaLspOptimizationCode,
}
impl LuaLspOptimizationAttribute {
pub fn is_check_table_field(self) -> bool {
self.code == LuaLspOptimizationCode::CheckTableField
}
pub fn is_delayed_definition(self) -> bool {
self.code == LuaLspOptimizationCode::DelayedDefinition
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct LuaIndexAliasAttribute<'a> {
pub name: &'a str,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct LuaConstructorAttribute<'a> {
pub name: &'a str,
pub root_class: Option<&'a str>,
pub strip_self: bool,
pub return_mode: LuaConstructorReturnMode,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LuaConstructorReturnMode {
SelfType,
Doc,
Default,
}
impl LuaConstructorReturnMode {
pub fn from_name(name: &str) -> Option<Self> {
match name {
"self" => Some(Self::SelfType),
"doc" => Some(Self::Doc),
"default" => Some(Self::Default),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LuaFieldAccessorConvention {
CamelCase,
PascalCase,
SnakeCase,
}
impl LuaFieldAccessorConvention {
pub fn from_name(name: &str) -> Option<Self> {
match name {
"camelCase" => Some(Self::CamelCase),
"PascalCase" => Some(Self::PascalCase),
"snake_case" => Some(Self::SnakeCase),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct LuaFieldAccessorAttribute<'a> {
pub convention: LuaFieldAccessorConvention,
pub getter: Option<&'a str>,
pub setter: Option<&'a str>,
}
#[cfg(test)]
mod tests {
use smol_str::SmolStr;
use super::{
LuaAttributeUse, LuaConstructorReturnMode, LuaFieldAccessorConvention,
LuaLspOptimizationCode,
};
use crate::{LuaType, LuaTypeDeclId};
fn doc_string(value: &str) -> LuaType {
LuaType::DocStringConst(SmolStr::new(value).into())
}
#[test]
fn constructor_attribute_uses_builtin_defaults() {
let attribute = LuaAttributeUse::new(
LuaTypeDeclId::global("constructor"),
vec![("name".into(), Some(doc_string("__init")))],
);
let constructor = attribute.as_constructor().unwrap();
assert_eq!(constructor.name, "__init");
assert_eq!(constructor.root_class, None);
assert!(constructor.strip_self);
assert_eq!(constructor.return_mode, LuaConstructorReturnMode::Default);
}
#[test]
fn constructor_attribute_supports_string_return_mode() {
let attribute = LuaAttributeUse::new(
LuaTypeDeclId::global("constructor"),
vec![
("name".into(), Some(doc_string("__init"))),
("return_mode".into(), Some(doc_string("doc"))),
],
);
let constructor = attribute.as_constructor().unwrap();
assert_eq!(constructor.return_mode, LuaConstructorReturnMode::Doc);
}
#[test]
fn field_accessor_defaults_to_camel_case() {
let attribute = LuaAttributeUse::new(LuaTypeDeclId::global("field_accessor"), Vec::new());
let field_accessor = attribute.as_field_accessor().unwrap();
assert_eq!(
field_accessor.convention,
LuaFieldAccessorConvention::CamelCase
);
assert_eq!(field_accessor.getter, None);
assert_eq!(field_accessor.setter, None);
}
#[test]
fn lsp_optimization_parses_known_codes() {
let attribute = LuaAttributeUse::new(
LuaTypeDeclId::global("lsp_optimization"),
vec![("code".into(), Some(doc_string("delayed_definition")))],
);
let lsp_optimization = attribute.as_lsp_optimization().unwrap();
assert_eq!(
lsp_optimization.code,
LuaLspOptimizationCode::DelayedDefinition
);
}
}