diff --git a/ideal.d.ts b/ideal.d.ts new file mode 100644 index 0000000..e05246f --- /dev/null +++ b/ideal.d.ts @@ -0,0 +1,18 @@ +declare namespace Elm { + class Main { + static init({ + node: Element, + flags: { currentTimeMillis: number, notificationPermission: string }, + }): Main; + + ports: { + changeDocument: { subscribe(callback: (value: unknown) => void): void }; + docFromAutomerge: { send(value: unknown): void }; + gotNewNotificationsPermission: { send(value: string): void }; + requestNotificationsPermission: { + subscribe(callback: (value: null) => void): void; + }; + sendNotification: { subscribe(callback: (value: unknown) => void): void }; + }; + } +} diff --git a/src/typescript.rs b/src/typescript.rs index 6573987..89ccabf 100644 --- a/src/typescript.rs +++ b/src/typescript.rs @@ -12,10 +12,26 @@ pub enum TSType { args: BTreeMap, returning: Box, }, + + // For the following members, we're making no effort to constrain what's valid where. That's up + // to our tests! TypeDecl { name: String, definition: Box, }, + NamespaceDecl { + name: String, + members: Vec, + }, + ClassDecl { + name: String, + members: Vec, + }, + MethodDecl { + is_static: bool, + name: String, + function: Box, // in practice, should always be a `Function` + }, } impl TSType { @@ -88,6 +104,39 @@ impl TSType { out.push_str(" = "); out.push_str(&definition.to_source()); } + Self::NamespaceDecl { name, members } => { + out.push_str("declare namespace "); + out.push_str(name); // TODO: escape? + out.push_str(" {\n"); + for member in members { + out.push_str(" "); + out.push_str(&member.to_source().replace('\n', "\n ")); + out.push('\n'); + } + out.push('}'); + } + Self::ClassDecl { name, members } => { + out.push_str("class "); + out.push_str(name); // TODO: escape? + out.push_str(" {\n"); + for member in members { + out.push_str(" "); + out.push_str(&member.to_source().replace('\n', "\n ")); + out.push('\n'); + } + out.push('}'); + } + Self::MethodDecl { + is_static, + name, + function, + } => { + if *is_static { + out.push_str("static "); + } + out.push_str(name); // TODO: escape? + out.push_str(&function.to_source()); + } } out @@ -117,6 +166,27 @@ impl TSType { Self::TypeRef(name) } + pub fn new_namespace(name: String, members: Vec) -> Self { + Self::NamespaceDecl { name, members } + } + + pub fn new_class(name: String, members: Vec) -> Self { + Self::ClassDecl { name, members } + } + + pub fn new_method( + is_static: bool, + name: String, + args: BTreeMap, + returning: TSType, + ) -> Self { + Self::MethodDecl { + is_static, + name, + function: Box::from(Self::new_function(args, returning)), + } + } + pub fn into_init(self) -> Self { Self::new_init(self) } @@ -180,7 +250,7 @@ mod tests { } #[test] - fn new_function() { + fn function_to_source() { let type_ = TSType::new_function( BTreeMap::from([ ("one".to_string(), TSType::Scalar("number")), @@ -196,7 +266,7 @@ mod tests { } #[test] - fn to_typedecl() { + fn typedecl_to_source() { let type_ = TSType::from_schema(from_json(json!({"properties": {"a": {"type": "string"}}}))) .into_typedecl("Flags".to_string()); @@ -206,4 +276,47 @@ mod tests { "type Flags = {\n a: string;\n}".to_string(), ) } + + #[test] + fn method_to_source() { + let method = TSType::new_method( + true, + "init".to_string(), + BTreeMap::new(), + TSType::Scalar("void"), + ); + + assert_eq!(method.to_source(), "static init(): void".to_string()); + } + + #[test] + fn class_to_source() { + let class = TSType::new_class( + "Main".to_string(), + Vec::from([TSType::new_method( + true, + "init".to_string(), + BTreeMap::new(), + TSType::Scalar("void"), + )]), + ); + + assert_eq!( + class.to_source(), + "class Main {\n static init(): void\n}".to_string() + ); + } + + #[test] + fn namespace_to_source() { + let namespace = TSType::new_namespace( + "Elm".to_string(), + Vec::from([TSType::new_class("Main".to_string(), Vec::new())]), + ); + + assert_eq!( + namespace.to_source(), + "declare namespace Elm {\n class Main {\n }\n}".to_string() + ); + } }