1use crate::{
2 analyse::TargetSupport,
3 build::{Origin, Target},
4 config::PackageConfig,
5 javascript::*,
6 uid::UniqueIdGenerator,
7 warning::{TypeWarningEmitter, WarningEmitter},
8};
9use camino::{Utf8Path, Utf8PathBuf};
10
11mod assert;
12mod assignments;
13mod bit_arrays;
14mod blocks;
15mod bools;
16mod case;
17mod case_clause_guards;
18mod consts;
19mod custom_types;
20mod echo;
21mod externals;
22mod functions;
23mod generics;
24mod lists;
25mod modules;
26mod numbers;
27mod panic;
28mod prelude;
29mod records;
30mod recursion;
31mod results;
32mod strings;
33mod todo;
34mod tuples;
35mod type_alias;
36mod use_;
37
38pub static CURRENT_PACKAGE: &str = "thepackage";
39
40#[macro_export]
41macro_rules! assert_js {
42 ($(($name:literal, $module_src:literal)),+, $src:literal $(,)?) => {
43 let compiled =
44 $crate::javascript::tests::compile_js($src, vec![$(($crate::javascript::tests::CURRENT_PACKAGE, $name, $module_src)),*]).expect("compilation failed");
45 let mut output = String::from("----- SOURCE CODE\n");
46 for (name, src) in [$(($name, $module_src)),*] {
47 output.push_str(&format!("-- {name}.gleam\n{src}\n\n"));
48 }
49 output.push_str(&format!("-- main.gleam\n{}\n\n----- COMPILED JAVASCRIPT\n{compiled}", $src));
50 insta::assert_snapshot!(insta::internals::AutoName, output, $src);
51 };
52
53 (($dep_package:expr, $dep_name:expr, $dep_src:expr), $src:expr $(,)?) => {{
54 let compiled =
55 $crate::javascript::tests::compile_js($src, vec![($dep_package, $dep_name, $dep_src)])
56 .expect("compilation failed");
57 let output = format!(
58 "----- SOURCE CODE\n{}\n\n----- COMPILED JAVASCRIPT\n{}",
59 $src, compiled
60 );
61 insta::assert_snapshot!(insta::internals::AutoName, output, $src);
62 }};
63
64 (($dep_package:expr, $dep_name:expr, $dep_src:expr), $src:expr, $js:expr $(,)?) => {{
65 let output =
66 $crate::javascript::tests::compile_js($src, Some(($dep_package, $dep_name, $dep_src)))
67 .expect("compilation failed");
68 assert_eq!(($src, output), ($src, $js.to_string()));
69 }};
70
71 ($src:expr $(,)?) => {{
72 let compiled =
73 $crate::javascript::tests::compile_js($src, vec![]).expect("compilation failed");
74 let output = format!(
75 "----- SOURCE CODE\n{}\n\n----- COMPILED JAVASCRIPT\n{}",
76 $src, compiled
77 );
78 insta::assert_snapshot!(insta::internals::AutoName, output, $src);
79 }};
80
81 ($src:expr, $js:expr $(,)?) => {{
82 let output =
83 $crate::javascript::tests::compile_js($src, vec![]).expect("compilation failed");
84 assert_eq!(($src, output), ($src, $js.to_string()));
85 }};
86}
87
88#[macro_export]
89macro_rules! assert_ts_def {
90 (($dep_1_package:expr, $dep_1_name:expr, $dep_1_src:expr), ($dep_2_package:expr, $dep_2_name:expr, $dep_2_src:expr), $src:expr $(,)?) => {{
91 let compiled = $crate::javascript::tests::compile_ts(
92 $src,
93 vec![
94 ($dep_1_package, $dep_1_name, $dep_1_src),
95 ($dep_2_package, $dep_2_name, $dep_2_src),
96 ],
97 )
98 .expect("compilation failed");
99 let output = format!(
100 "----- SOURCE CODE\n{}\n\n----- TYPESCRIPT DEFINITIONS\n{}",
101 $src, compiled
102 );
103 insta::assert_snapshot!(insta::internals::AutoName, output, $src);
104 }};
105
106 (($dep_package:expr, $dep_name:expr, $dep_src:expr), $src:expr $(,)?) => {{
107 let compiled =
108 $crate::javascript::tests::compile_ts($src, vec![($dep_package, $dep_name, $dep_src)])
109 .expect("compilation failed");
110 let output = format!(
111 "----- SOURCE CODE\n{}\n\n----- TYPESCRIPT DEFINITIONS\n{}",
112 $src, compiled
113 );
114 insta::assert_snapshot!(insta::internals::AutoName, output, $src);
115 }};
116
117 ($src:expr $(,)?) => {{
118 let compiled =
119 $crate::javascript::tests::compile_ts($src, vec![]).expect("compilation failed");
120 let output = format!(
121 "----- SOURCE CODE\n{}\n\n----- TYPESCRIPT DEFINITIONS\n{}",
122 $src, compiled
123 );
124 insta::assert_snapshot!(insta::internals::AutoName, output, $src);
125 }};
126}
127
128pub fn compile(src: &str, deps: Vec<(&str, &str, &str)>) -> TypedModule {
129 let mut modules = im::HashMap::new();
130 let ids = UniqueIdGenerator::new();
131 // DUPE: preludeinsertion
132 // TODO: Currently we do this here and also in the tests. It would be better
133 // to have one place where we create all this required state for use in each
134 // place.
135 let _ = modules.insert(
136 PRELUDE_MODULE_NAME.into(),
137 crate::type_::build_prelude(&ids),
138 );
139 let mut direct_dependencies = std::collections::HashMap::from_iter(vec![]);
140
141 deps.iter().for_each(|(dep_package, dep_name, dep_src)| {
142 let mut dep_config = PackageConfig::default();
143 dep_config.name = (*dep_package).into();
144 let parsed = crate::parse::parse_module(
145 Utf8PathBuf::from("test/path"),
146 dep_src,
147 &WarningEmitter::null(),
148 )
149 .expect("dep syntax error");
150 let mut ast = parsed.module;
151 ast.name = (*dep_name).into();
152 let line_numbers = LineNumbers::new(dep_src);
153
154 let dep = crate::analyse::ModuleAnalyzerConstructor::<()> {
155 target: Target::JavaScript,
156 ids: &ids,
157 origin: Origin::Src,
158 importable_modules: &modules,
159 warnings: &TypeWarningEmitter::null(),
160 direct_dependencies: &std::collections::HashMap::new(),
161 target_support: TargetSupport::Enforced,
162 package_config: &dep_config,
163 }
164 .infer_module(ast, line_numbers, "".into())
165 .expect("should successfully infer");
166 let _ = modules.insert((*dep_name).into(), dep.type_info);
167 let _ = direct_dependencies.insert((*dep_package).into(), ());
168 });
169
170 let parsed =
171 crate::parse::parse_module(Utf8PathBuf::from("test/path"), src, &WarningEmitter::null())
172 .expect("syntax error");
173 let mut ast = parsed.module;
174 ast.name = "my/mod".into();
175 let line_numbers = LineNumbers::new(src);
176 let mut config = PackageConfig::default();
177 config.name = "thepackage".into();
178
179 crate::analyse::ModuleAnalyzerConstructor::<()> {
180 target: Target::JavaScript,
181 ids: &ids,
182 origin: Origin::Src,
183 importable_modules: &modules,
184 warnings: &TypeWarningEmitter::null(),
185 direct_dependencies: &direct_dependencies,
186 target_support: TargetSupport::NotEnforced,
187 package_config: &config,
188 }
189 .infer_module(ast, line_numbers, "src/module.gleam".into())
190 .expect("should successfully infer")
191}
192
193pub fn compile_js(src: &str, deps: Vec<(&str, &str, &str)>) -> Result<String, crate::Error> {
194 let ast = compile(src, deps);
195 let line_numbers = LineNumbers::new(src);
196 let stdlib_package = StdlibPackage::Present;
197 let output = module(ModuleConfig {
198 module: &ast,
199 line_numbers: &line_numbers,
200 src: &"".into(),
201 target_support: TargetSupport::Enforced,
202 typescript: TypeScriptDeclarations::None,
203 stdlib_package,
204 path: Utf8Path::new("src/module.gleam"),
205 project_root: "project/root".into(),
206 })?;
207
208 Ok(output.replace(
209 std::include_str!("../../templates/echo.mjs"),
210 "// ...omitted code from `templates/echo.mjs`...",
211 ))
212}
213
214pub fn compile_ts(src: &str, deps: Vec<(&str, &str, &str)>) -> Result<String, crate::Error> {
215 let ast = compile(src, deps);
216 ts_declaration(&ast, Utf8Path::new(""), &src.into())
217}