⭐️ A friendly language for building type-safe, scalable systems!
at main 6.2 kB view raw
1use std::collections::{HashMap, HashSet}; 2 3use ecow::EcoString; 4use itertools::Itertools; 5 6use crate::{ 7 docvec, 8 javascript::{INDENT, JavaScriptCodegenTarget}, 9 pretty::{Document, Documentable, break_, concat, join, line}, 10}; 11 12/// A collection of JavaScript import statements from Gleam imports and from 13/// external functions, to be rendered into a JavaScript module. 14/// 15#[derive(Debug, Default)] 16pub(crate) struct Imports<'a> { 17 imports: HashMap<EcoString, Import<'a>>, 18 exports: HashSet<EcoString>, 19} 20 21impl<'a> Imports<'a> { 22 pub fn new() -> Self { 23 Self::default() 24 } 25 26 pub fn register_export(&mut self, export: EcoString) { 27 let _ = self.exports.insert(export); 28 } 29 30 pub fn register_module( 31 &mut self, 32 path: EcoString, 33 aliases: impl IntoIterator<Item = EcoString>, 34 unqualified_imports: impl IntoIterator<Item = Member<'a>>, 35 ) { 36 let import = self 37 .imports 38 .entry(path.clone()) 39 .or_insert_with(|| Import::new(path.clone())); 40 import.aliases.extend(aliases); 41 import.unqualified.extend(unqualified_imports) 42 } 43 44 pub fn into_doc(self, codegen_target: JavaScriptCodegenTarget) -> Document<'a> { 45 let imports = concat( 46 self.imports 47 .into_values() 48 .sorted_by(|a, b| a.path.cmp(&b.path)) 49 .map(|import| Import::into_doc(import, codegen_target)), 50 ); 51 52 if self.exports.is_empty() { 53 imports 54 } else { 55 let names = join( 56 self.exports 57 .into_iter() 58 .sorted() 59 .map(|string| string.to_doc()), 60 break_(",", ", "), 61 ); 62 let names = docvec![ 63 docvec![break_("", " "), names].nest(INDENT), 64 break_(",", " ") 65 ] 66 .group(); 67 imports 68 .append(line()) 69 .append("export {") 70 .append(names) 71 .append("};") 72 .append(line()) 73 } 74 } 75 76 pub fn is_empty(&self) -> bool { 77 self.imports.is_empty() && self.exports.is_empty() 78 } 79} 80 81#[derive(Debug)] 82struct Import<'a> { 83 path: EcoString, 84 aliases: HashSet<EcoString>, 85 unqualified: Vec<Member<'a>>, 86} 87 88impl<'a> Import<'a> { 89 fn new(path: EcoString) -> Self { 90 Self { 91 path, 92 aliases: Default::default(), 93 unqualified: Default::default(), 94 } 95 } 96 97 pub fn into_doc(self, codegen_target: JavaScriptCodegenTarget) -> Document<'a> { 98 let path = self.path.to_doc(); 99 let import_modifier = if codegen_target == JavaScriptCodegenTarget::TypeScriptDeclarations { 100 "type " 101 } else { 102 "" 103 }; 104 let alias_imports = concat(self.aliases.into_iter().sorted().map(|alias| { 105 docvec![ 106 "import ", 107 import_modifier, 108 "* as ", 109 alias, 110 " from \"", 111 path.clone(), 112 r#"";"#, 113 line() 114 ] 115 })); 116 if self.unqualified.is_empty() { 117 alias_imports 118 } else { 119 let members = self.unqualified.into_iter().map(Member::into_doc); 120 let members = join(members, break_(",", ", ")); 121 let members = docvec![ 122 docvec![break_("", " "), members].nest(INDENT), 123 break_(",", " ") 124 ] 125 .group(); 126 docvec![ 127 alias_imports, 128 "import ", 129 import_modifier, 130 "{", 131 members, 132 "} from \"", 133 path, 134 r#"";"#, 135 line() 136 ] 137 } 138 } 139} 140 141#[derive(Debug)] 142pub struct Member<'a> { 143 pub name: Document<'a>, 144 pub alias: Option<Document<'a>>, 145} 146 147impl<'a> Member<'a> { 148 fn into_doc(self) -> Document<'a> { 149 match self.alias { 150 None => self.name, 151 Some(alias) => docvec![self.name, " as ", alias], 152 } 153 } 154} 155 156#[test] 157fn into_doc() { 158 let mut imports = Imports::new(); 159 imports.register_module("./gleam/empty".into(), [], []); 160 imports.register_module( 161 "./multiple/times".into(), 162 ["wibble".into(), "wobble".into()], 163 [], 164 ); 165 imports.register_module("./multiple/times".into(), ["wubble".into()], []); 166 imports.register_module( 167 "./multiple/times".into(), 168 [], 169 [Member { 170 name: "one".to_doc(), 171 alias: None, 172 }], 173 ); 174 175 imports.register_module( 176 "./other".into(), 177 [], 178 [ 179 Member { 180 name: "one".to_doc(), 181 alias: None, 182 }, 183 Member { 184 name: "one".to_doc(), 185 alias: Some("onee".to_doc()), 186 }, 187 Member { 188 name: "two".to_doc(), 189 alias: Some("twoo".to_doc()), 190 }, 191 ], 192 ); 193 194 imports.register_module( 195 "./other".into(), 196 [], 197 [ 198 Member { 199 name: "three".to_doc(), 200 alias: None, 201 }, 202 Member { 203 name: "four".to_doc(), 204 alias: None, 205 }, 206 ], 207 ); 208 209 imports.register_module( 210 "./zzz".into(), 211 [], 212 [ 213 Member { 214 name: "one".to_doc(), 215 alias: None, 216 }, 217 Member { 218 name: "two".to_doc(), 219 alias: None, 220 }, 221 ], 222 ); 223 224 assert_eq!( 225 line() 226 .append(imports.into_doc(JavaScriptCodegenTarget::JavaScript)) 227 .to_pretty_string(40), 228 r#" 229import * as wibble from "./multiple/times"; 230import * as wobble from "./multiple/times"; 231import * as wubble from "./multiple/times"; 232import { one } from "./multiple/times"; 233import { 234 one, 235 one as onee, 236 two as twoo, 237 three, 238 four, 239} from "./other"; 240import { one, two } from "./zzz"; 241"# 242 .to_string() 243 ); 244}