Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / javascript / import.rs
8.1 kB 293 lines
1// SPDX-License-Identifier: Apache-2.0 2// SPDX-FileCopyrightText: 2021 The Gleam contributors 3 4use std::collections::{HashMap, HashSet}; 5 6use ecow::EcoString; 7use itertools::Itertools; 8 9use crate::javascript::{INDENT, JavaScriptCodegenTarget}; 10use pretty_arena::*; 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, 'doc> { 17 imports: HashMap<EcoString, Import<'a, 'doc>>, 18 exports: HashSet<EcoString>, 19} 20 21impl<'a, 'doc> Imports<'a, 'doc> { 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, 'doc>>, 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( 45 self, 46 arena: &'doc DocumentArena<'a, 'doc>, 47 codegen_target: JavaScriptCodegenTarget, 48 ) -> Document<'a, 'doc> { 49 let imports = arena.concat( 50 self.imports 51 .into_values() 52 .sorted_by(|a, b| a.path.cmp(&b.path)) 53 .map(|import| import.into_doc(arena, codegen_target)), 54 ); 55 56 if self.exports.is_empty() { 57 imports 58 } else { 59 let names = arena.join( 60 self.exports 61 .into_iter() 62 .sorted() 63 .map(|string| string.to_doc(arena)), 64 COMMA_BREAK_DOCUMENT, 65 ); 66 let names = docvec![ 67 arena, 68 docvec![arena, BREAKABLE_SPACE_DOCUMENT, names].nest(arena, INDENT), 69 TRAILING_COMMA_OR_SPACE_BREAK_DOCUMENT 70 ] 71 .group(arena); 72 73 let export_keyword = match codegen_target { 74 JavaScriptCodegenTarget::JavaScript => EXPORT_SPACE_OPEN_CURLY_DOCUMENT, 75 JavaScriptCodegenTarget::TypeScriptDeclarations => { 76 EXPORT_TYPE_SPACE_OPEN_CURLY_DOCUMENT 77 } 78 }; 79 80 docvec![ 81 arena, 82 imports, 83 LINE_DOCUMENT, 84 export_keyword, 85 names, 86 CLOSE_CURLY_SEMICOLON_DOCUMENT, 87 LINE_DOCUMENT 88 ] 89 } 90 } 91 92 pub fn is_empty(&self) -> bool { 93 self.imports.is_empty() && self.exports.is_empty() 94 } 95 96 /// Remove variants which are imported in Gleam code, but not needed to be 97 /// imported because singleton constants are used instead. 98 /// 99 pub fn filter_unused_variants<I>(&mut self, unused: I) 100 where 101 I: Iterator<Item = (EcoString, EcoString)>, 102 { 103 for (path, name) in unused { 104 if let Some(import_) = self.imports.get_mut(&path) 105 && let Some(index) = import_ 106 .unqualified 107 .iter() 108 .position(|member| member.name == name) 109 { 110 _ = import_.unqualified.remove(index); 111 } 112 } 113 } 114} 115 116#[derive(Debug)] 117struct Import<'a, 'doc> { 118 path: EcoString, 119 aliases: HashSet<EcoString>, 120 unqualified: Vec<Member<'a, 'doc>>, 121} 122 123impl<'a, 'doc> Import<'a, 'doc> { 124 fn new(path: EcoString) -> Self { 125 Self { 126 path, 127 aliases: HashSet::new(), 128 unqualified: vec![], 129 } 130 } 131 132 pub fn into_doc( 133 self, 134 arena: &'doc DocumentArena<'a, 'doc>, 135 codegen_target: JavaScriptCodegenTarget, 136 ) -> Document<'a, 'doc> { 137 let path = self.path.to_doc(arena); 138 let import_modifier = if codegen_target == JavaScriptCodegenTarget::TypeScriptDeclarations { 139 TYPE_SPACE_DOCUMENT 140 } else { 141 EMPTY_DOCUMENT 142 }; 143 let alias_imports = arena.concat(self.aliases.into_iter().sorted().map(|alias| { 144 docvec![ 145 arena, 146 IMPORT_SPACE_DOCUMENT, 147 import_modifier, 148 TIMES_SPACE_AS_SPACE_DOCUMENT, 149 alias, 150 SPACE_FROM_SPACE_DOUBLE_QUOTE_DOCUMENT, 151 path, 152 DOUBLE_QUOTE_SEMICOLON_DOCUMENT, 153 LINE_DOCUMENT 154 ] 155 })); 156 if self.unqualified.is_empty() { 157 alias_imports 158 } else { 159 let members = self 160 .unqualified 161 .into_iter() 162 .map(|member| member.into_doc(arena)); 163 let members = arena.join(members, COMMA_BREAK_DOCUMENT); 164 let members = docvec![ 165 arena, 166 docvec![arena, BREAKABLE_SPACE_DOCUMENT, members].nest(arena, INDENT), 167 TRAILING_COMMA_OR_SPACE_BREAK_DOCUMENT 168 ] 169 .group(arena); 170 docvec![ 171 arena, 172 alias_imports, 173 IMPORT_SPACE_DOCUMENT, 174 import_modifier, 175 OPEN_CURLY_DOCUMENT, 176 members, 177 CLOSE_CURLY_SPACE_FROM_SPACE_DOUBLE_QUOTE_DOCUMENT, 178 path, 179 DOUBLE_QUOTE_SEMICOLON_DOCUMENT, 180 LINE_DOCUMENT 181 ] 182 } 183 } 184} 185 186#[derive(Debug)] 187pub struct Member<'a, 'doc> { 188 pub name: EcoString, 189 pub alias: Option<Document<'a, 'doc>>, 190} 191 192impl<'a, 'doc> Member<'a, 'doc> { 193 fn into_doc(self, arena: &'doc DocumentArena<'a, 'doc>) -> Document<'a, 'doc> { 194 match self.alias { 195 None => self.name.to_doc(arena), 196 Some(alias) => docvec![arena, self.name, SPACE_AS_SPACE_DOCUMENT, alias], 197 } 198 } 199} 200 201#[test] 202fn into_doc() { 203 let arena = DocumentArena::new(); 204 let mut imports = Imports::new(); 205 imports.register_module("./gleam/empty".into(), [], []); 206 imports.register_module( 207 "./multiple/times".into(), 208 ["wibble".into(), "wobble".into()], 209 [], 210 ); 211 imports.register_module("./multiple/times".into(), ["wubble".into()], []); 212 imports.register_module( 213 "./multiple/times".into(), 214 [], 215 [Member { 216 name: "one".into(), 217 alias: None, 218 }], 219 ); 220 221 imports.register_module( 222 "./other".into(), 223 [], 224 [ 225 Member { 226 name: "one".into(), 227 alias: None, 228 }, 229 Member { 230 name: "one".into(), 231 alias: Some("onee".to_doc(&arena)), 232 }, 233 Member { 234 name: "two".into(), 235 alias: Some("twoo".to_doc(&arena)), 236 }, 237 ], 238 ); 239 240 imports.register_module( 241 "./other".into(), 242 [], 243 [ 244 Member { 245 name: "three".into(), 246 alias: None, 247 }, 248 Member { 249 name: "four".into(), 250 alias: None, 251 }, 252 ], 253 ); 254 255 imports.register_module( 256 "./zzz".into(), 257 [], 258 [ 259 Member { 260 name: "one".into(), 261 alias: None, 262 }, 263 Member { 264 name: "two".into(), 265 alias: None, 266 }, 267 ], 268 ); 269 270 assert_eq!( 271 LINE_DOCUMENT 272 .append( 273 &arena, 274 imports.into_doc(&arena, JavaScriptCodegenTarget::JavaScript) 275 ) 276 .to_pretty_string(40), 277 r#" 278import * as wibble from "./multiple/times"; 279import * as wobble from "./multiple/times"; 280import * as wubble from "./multiple/times"; 281import { one } from "./multiple/times"; 282import { 283 one, 284 one as onee, 285 two as twoo, 286 three, 287 four, 288} from "./other"; 289import { one, two } from "./zzz"; 290"# 291 .to_string() 292 ); 293}