1use std::fmt::Debug;
4
5use ecow::{eco_vec, EcoVec};
6use thiserror::Error;
7use typst::diag::{FileResult, Severity, SourceDiagnostic, Warned};
8use typst::foundations::{Bytes, Datetime};
9use typst::layout::PagedDocument;
10use typst::syntax::{FileId, Source};
11use typst::text::{Font, FontBook};
12use typst::utils::LazyHash;
13use typst::{Library, World};
14use tytanic_utils::fmt::Term;
15
16#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub enum Warnings {
19 Ignore,
21
22 #[default]
24 Emit,
25
26 Promote,
28}
29
30#[derive(Debug, Clone, Error)]
33#[error("compilation failed with {} {}", .0.len(), Term::simple("error").with(.0.len()))]
34pub struct Error(pub EcoVec<SourceDiagnostic>);
35
36pub fn compile(
38 source: Source,
39 world: &dyn World,
40 warnings: Warnings,
41) -> Warned<Result<PagedDocument, Error>> {
42 struct TestWorldAdapter<'s, 'w> {
43 source: &'s Source,
44 global: &'w dyn World,
45 }
46
47 impl World for TestWorldAdapter<'_, '_> {
48 fn library(&self) -> &LazyHash<Library> {
49 self.global.library()
50 }
51
52 fn book(&self) -> &LazyHash<FontBook> {
53 self.global.book()
54 }
55
56 fn main(&self) -> FileId {
57 self.source.id()
58 }
59
60 fn source(&self, id: FileId) -> FileResult<Source> {
61 if id == self.source.id() {
62 Ok(self.source.clone())
63 } else {
64 self.global.source(id)
65 }
66 }
67
68 fn file(&self, id: FileId) -> FileResult<Bytes> {
69 self.global.file(id)
70 }
71
72 fn font(&self, index: usize) -> Option<Font> {
73 self.global.font(index)
74 }
75
76 fn today(&self, offset: Option<i64>) -> Option<Datetime> {
77 self.global.today(offset)
78 }
79 }
80
81 let Warned {
82 output,
83 warnings: mut emitted,
84 } = typst::compile(&TestWorldAdapter {
85 source: &source,
86 global: world,
87 });
88
89 match warnings {
90 Warnings::Ignore => Warned {
91 output: output.map_err(Error),
92 warnings: eco_vec![],
93 },
94 Warnings::Emit => Warned {
95 output: output.map_err(Error),
96 warnings: emitted,
97 },
98 Warnings::Promote => {
99 emitted = emitted
100 .into_iter()
101 .map(|mut warning| {
102 warning.severity = Severity::Error;
103 warning.with_hint("this warning was promoted to an error")
104 })
105 .collect();
106
107 match output {
108 Ok(doc) if emitted.is_empty() => Warned {
109 output: Ok(doc),
110 warnings: eco_vec![],
111 },
112 Ok(_) => Warned {
113 output: Err(Error(emitted)),
114 warnings: eco_vec![],
115 },
116 Err(errors) => {
117 emitted.extend(errors);
118 Warned {
119 output: Err(Error(emitted)),
120 warnings: eco_vec![],
121 }
122 }
123 }
124 }
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use crate::_dev::VirtualWorld;
132
133 const TEST_PASS: &str = "Hello World";
134 const TEST_WARN: &str = "#set text(font: \"foo\"); Hello World";
135 const TEST_FAIL: &str = "#set text(font: \"foo\"); #panic()";
136
137 #[test]
138 fn test_compile_pass_ignore_warnings() {
139 let world = VirtualWorld::default();
140 let source = Source::detached(TEST_PASS);
141
142 let Warned { output, warnings } = compile(source, &world, Warnings::Ignore);
143 assert!(output.is_ok());
144 assert!(warnings.is_empty());
145 }
146
147 #[test]
148 fn test_compile_pass_emit_warnings() {
149 let world = VirtualWorld::default();
150 let source = Source::detached(TEST_PASS);
151
152 let Warned { output, warnings } = compile(source, &world, Warnings::Emit);
153 assert!(output.is_ok());
154 assert!(warnings.is_empty());
155 }
156
157 #[test]
158 fn test_compile_pass_promote_warnings() {
159 let world = VirtualWorld::default();
160 let source = Source::detached(TEST_PASS);
161
162 let Warned { output, warnings } = compile(source, &world, Warnings::Promote);
163 assert!(output.is_ok());
164 assert!(warnings.is_empty());
165 }
166
167 #[test]
168 fn test_compile_warn_ignore_warnings() {
169 let world = VirtualWorld::default();
170 let source = Source::detached(TEST_WARN);
171
172 let Warned { output, warnings } = compile(source, &world, Warnings::Ignore);
173 assert!(output.is_ok());
174 assert!(warnings.is_empty());
175 }
176
177 #[test]
178 fn test_compile_warn_emit_warnings() {
179 let world = VirtualWorld::default();
180 let source = Source::detached(TEST_WARN);
181
182 let Warned { output, warnings } = compile(source, &world, Warnings::Emit);
183 assert!(output.is_ok());
184 assert_eq!(warnings.len(), 1);
185 }
186
187 #[test]
188 fn test_compile_warn_promote_warnings() {
189 let world = VirtualWorld::default();
190 let source = Source::detached(TEST_WARN);
191
192 let Warned { output, warnings } = compile(source, &world, Warnings::Promote);
193 assert_eq!(output.unwrap_err().0.len(), 1);
194 assert!(warnings.is_empty());
195 }
196
197 #[test]
198 fn test_compile_fail_ignore_warnings() {
199 let world = VirtualWorld::default();
200 let source = Source::detached(TEST_FAIL);
201
202 let Warned { output, warnings } = compile(source, &world, Warnings::Ignore);
203 assert_eq!(output.unwrap_err().0.len(), 1);
204 assert!(warnings.is_empty());
205 }
206
207 #[test]
208 fn test_compile_fail_emit_warnings() {
209 let world = VirtualWorld::default();
210 let source = Source::detached(TEST_FAIL);
211
212 let Warned { output, warnings } = compile(source, &world, Warnings::Emit);
213 assert_eq!(output.unwrap_err().0.len(), 1);
214 assert_eq!(warnings.len(), 1);
215 }
216
217 #[test]
218 fn test_compile_fail_promote_warnings() {
219 let world = VirtualWorld::default();
220 let source = Source::detached(TEST_FAIL);
221
222 let Warned { output, warnings } = compile(source, &world, Warnings::Promote);
223 assert_eq!(output.unwrap_err().0.len(), 2);
224 assert!(warnings.is_empty());
225 }
226}