Skip to main content

ts_gen/parse/
classify.rs

1//! Interface classification: determine if an interface is class-like or a dictionary.
2
3use crate::ir::*;
4
5/// Classify an interface based on its members.
6///
7/// - **Dictionary**: Only has properties (getters/setters), no methods — gets
8///   builder pattern and property accessors. Includes interfaces with required
9///   properties (the builder validates them at runtime).
10/// - **ClassLike**: Has methods, constructors, or static members
11/// - **Unclassified**: Empty or unclear
12pub fn classify_interface(members: &[Member]) -> InterfaceClassification {
13    if members.is_empty() {
14        return InterfaceClassification::Unclassified;
15    }
16
17    let mut has_methods = false;
18    let mut has_properties = false;
19
20    for member in members {
21        match member {
22            Member::Method(_) | Member::StaticMethod(_) => {
23                has_methods = true;
24            }
25            Member::Constructor(_) => {
26                has_methods = true;
27            }
28            Member::Getter(_) | Member::Setter(_) => {
29                has_properties = true;
30            }
31            Member::StaticGetter(_) | Member::StaticSetter(_) => {
32                has_methods = true;
33            }
34            Member::IndexSignature(_) => {}
35        }
36    }
37
38    if has_methods {
39        InterfaceClassification::ClassLike
40    } else if has_properties {
41        InterfaceClassification::Dictionary
42    } else {
43        InterfaceClassification::Unclassified
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50
51    fn getter(name: &str, optional: bool) -> Member {
52        Member::Getter(GetterMember {
53            js_name: name.to_string(),
54            type_ref: TypeRef::Any,
55            optional,
56            doc: None,
57        })
58    }
59
60    fn static_getter(name: &str) -> Member {
61        Member::StaticGetter(StaticGetterMember {
62            js_name: name.to_string(),
63            type_ref: TypeRef::Any,
64            doc: None,
65        })
66    }
67
68    fn method(name: &str) -> Member {
69        Member::Method(MethodMember {
70            name: name.to_string(),
71            js_name: name.to_string(),
72            type_params: vec![],
73            params: vec![],
74            return_type: TypeRef::Void,
75            optional: false,
76            doc: None,
77        })
78    }
79
80    #[test]
81    fn empty_is_unclassified() {
82        assert_eq!(
83            classify_interface(&[]),
84            InterfaceClassification::Unclassified
85        );
86    }
87
88    #[test]
89    fn all_optional_getters_is_dictionary() {
90        let members = vec![getter("foo", true), getter("bar", true)];
91        assert_eq!(
92            classify_interface(&members),
93            InterfaceClassification::Dictionary
94        );
95    }
96
97    #[test]
98    fn required_getter_is_dictionary() {
99        // Properties-only interfaces are dictionaries regardless of optionality.
100        // The builder validates required properties at runtime.
101        let members = vec![getter("foo", false)];
102        assert_eq!(
103            classify_interface(&members),
104            InterfaceClassification::Dictionary
105        );
106    }
107
108    #[test]
109    fn methods_is_class_like() {
110        let members = vec![method("do_thing")];
111        assert_eq!(
112            classify_interface(&members),
113            InterfaceClassification::ClassLike
114        );
115    }
116
117    #[test]
118    fn static_getter_is_class_like() {
119        let members = vec![static_getter("instance")];
120        assert_eq!(
121            classify_interface(&members),
122            InterfaceClassification::ClassLike
123        );
124    }
125
126    #[test]
127    fn optional_getters_with_static_is_class_like() {
128        // Regression: static properties should prevent Dictionary classification
129        let members = vec![getter("foo", true), static_getter("bar")];
130        assert_eq!(
131            classify_interface(&members),
132            InterfaceClassification::ClassLike
133        );
134    }
135}