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