summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlolstork <137357423+lolstork@users.noreply.github.com>2023-07-19 12:52:47 +0200
committerGitHub <noreply@github.com>2023-07-19 12:52:47 +0200
commitb37c1e27314ed9b9341dd82c1bbc8238121c7578 (patch)
treecb8d9d4d179fdbcdbc94b3104d068cd21bf24074
parent8a57395ee48ecee02c2eb833d232979730f0e445 (diff)
Add infrastructure for compiler warnings (#1731)
-rw-r--r--crates/typst-cli/src/compile.rs55
-rw-r--r--crates/typst-cli/src/watch.rs5
-rw-r--r--crates/typst-docs/src/html.rs6
-rw-r--r--crates/typst/src/diag.rs78
-rw-r--r--crates/typst/src/eval/mod.rs61
-rw-r--r--crates/typst/src/eval/tracer.rs70
-rw-r--r--crates/typst/src/ide/analyze.rs49
-rw-r--r--crates/typst/src/lib.rs7
-rw-r--r--crates/typst/src/model/mod.rs6
-rw-r--r--tests/src/benches.rs8
-rw-r--r--tests/src/tests.rs66
-rw-r--r--tests/typ/lint/markup.typ13
12 files changed, 279 insertions, 145 deletions
diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs
index 9dfa4d41..f6819319 100644
--- a/crates/typst-cli/src/compile.rs
+++ b/crates/typst-cli/src/compile.rs
@@ -4,9 +4,9 @@ use std::path::Path;
use codespan_reporting::diagnostic::{Diagnostic, Label};
use codespan_reporting::term::{self, termcolor};
use termcolor::{ColorChoice, StandardStream};
-use typst::diag::{bail, SourceError, StrResult};
+use typst::diag::{bail, Severity, SourceDiagnostic, StrResult};
use typst::doc::Document;
-use typst::eval::eco_format;
+use typst::eval::{eco_format, Tracer};
use typst::geom::Color;
use typst::syntax::{FileId, Source};
use typst::World;
@@ -46,9 +46,13 @@ pub fn compile_once(
world.reset();
world.source(world.main()).map_err(|err| err.to_string())?;
- let result = typst::compile(world);
+ let mut tracer = Tracer::default();
+
+ let result = typst::compile(world, &mut tracer);
let duration = start.elapsed();
+ let warnings = tracer.warnings();
+
match result {
// Export the PDF / PNG.
Ok(document) => {
@@ -56,9 +60,16 @@ pub fn compile_once(
tracing::info!("Compilation succeeded in {duration:?}");
if watching {
- Status::Success(duration).print(command).unwrap();
+ if warnings.is_empty() {
+ Status::Success(duration).print(command).unwrap();
+ } else {
+ Status::PartialSuccess(duration).print(command).unwrap();
+ }
}
+ print_diagnostics(world, &[], &warnings, command.diagnostic_format)
+ .map_err(|_| "failed to print diagnostics")?;
+
if let Some(open) = command.open.take() {
open_file(open.as_deref(), &command.output())?;
}
@@ -73,7 +84,7 @@ pub fn compile_once(
Status::Error.print(command).unwrap();
}
- print_diagnostics(world, *errors, command.diagnostic_format)
+ print_diagnostics(world, &errors, &warnings, command.diagnostic_format)
.map_err(|_| "failed to print diagnostics")?;
}
}
@@ -143,7 +154,8 @@ fn open_file(open: Option<&str>, path: &Path) -> StrResult<()> {
/// Print diagnostic messages to the terminal.
fn print_diagnostics(
world: &SystemWorld,
- errors: Vec<SourceError>,
+ errors: &[SourceDiagnostic],
+ warnings: &[SourceDiagnostic],
diagnostic_format: DiagnosticFormat,
) -> Result<(), codespan_reporting::files::Error> {
let mut w = match diagnostic_format {
@@ -156,23 +168,28 @@ fn print_diagnostics(
config.display_style = term::DisplayStyle::Short;
}
- for error in errors {
- // The main diagnostic.
- let diag = Diagnostic::error()
- .with_message(error.message)
- .with_notes(
- error
- .hints
- .iter()
- .map(|e| (eco_format!("hint: {e}")).into())
- .collect(),
- )
- .with_labels(vec![Label::primary(error.span.id(), world.range(error.span))]);
+ for diagnostic in warnings.iter().chain(errors.iter()) {
+ let diag = match diagnostic.severity {
+ Severity::Error => Diagnostic::error(),
+ Severity::Warning => Diagnostic::warning(),
+ }
+ .with_message(diagnostic.message.clone())
+ .with_notes(
+ diagnostic
+ .hints
+ .iter()
+ .map(|e| (eco_format!("hint: {e}")).into())
+ .collect(),
+ )
+ .with_labels(vec![Label::primary(
+ diagnostic.span.id(),
+ world.range(diagnostic.span),
+ )]);
term::emit(&mut w, &config, world, &diag)?;
// Stacktrace-like helper diagnostics.
- for point in error.trace {
+ for point in &diagnostic.trace {
let message = point.v.to_string();
let help = Diagnostic::help().with_message(message).with_labels(vec![
Label::primary(point.span.id(), world.range(point.span)),
diff --git a/crates/typst-cli/src/watch.rs b/crates/typst-cli/src/watch.rs
index cf9c05ba..759d27ec 100644
--- a/crates/typst-cli/src/watch.rs
+++ b/crates/typst-cli/src/watch.rs
@@ -138,6 +138,7 @@ fn is_event_relevant(event: &notify::Event, output: &Path) -> bool {
pub enum Status {
Compiling,
Success(std::time::Duration),
+ PartialSuccess(std::time::Duration),
Error,
}
@@ -176,6 +177,9 @@ impl Status {
match self {
Self::Compiling => "compiling ...".into(),
Self::Success(duration) => format!("compiled successfully in {duration:.2?}"),
+ Self::PartialSuccess(duration) => {
+ format!("compiled with warnings in {duration:.2?}")
+ }
Self::Error => "compiled with errors".into(),
}
}
@@ -184,6 +188,7 @@ impl Status {
let styles = term::Styles::default();
match self {
Self::Error => styles.header_error,
+ Self::PartialSuccess(_) => styles.header_warning,
_ => styles.header_note,
}
}
diff --git a/crates/typst-docs/src/html.rs b/crates/typst-docs/src/html.rs
index ed49f9fe..b021d4a7 100644
--- a/crates/typst-docs/src/html.rs
+++ b/crates/typst-docs/src/html.rs
@@ -4,7 +4,7 @@ use comemo::Prehashed;
use pulldown_cmark as md;
use typed_arena::Arena;
use typst::diag::FileResult;
-use typst::eval::Datetime;
+use typst::eval::{Datetime, Tracer};
use typst::font::{Font, FontBook};
use typst::geom::{Point, Size};
use typst::syntax::{FileId, Source};
@@ -428,7 +428,9 @@ fn code_block(resolver: &dyn Resolver, lang: &str, text: &str) -> Html {
let id = FileId::new(None, Path::new("/main.typ"));
let source = Source::new(id, compile);
let world = DocWorld(source);
- let mut frames = match typst::compile(&world) {
+ let mut tracer = Tracer::default();
+
+ let mut frames = match typst::compile(&world, &mut tracer) {
Ok(doc) => doc.pages,
Err(err) => {
let msg = &err[0].message;
diff --git a/crates/typst/src/diag.rs b/crates/typst/src/diag.rs
index 85fb10a2..1cc4a045 100644
--- a/crates/typst/src/diag.rs
+++ b/crates/typst/src/diag.rs
@@ -33,7 +33,7 @@ macro_rules! __bail {
};
($span:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {
- return Err(Box::new(vec![$crate::diag::SourceError::new(
+ return Err(Box::new(vec![$crate::diag::SourceDiagnostic::error(
$span,
$crate::diag::eco_format!($fmt, $($arg),*),
)]))
@@ -43,7 +43,7 @@ macro_rules! __bail {
#[doc(inline)]
pub use crate::__bail as bail;
-/// Construct an [`EcoString`] or [`SourceError`].
+/// Construct an [`EcoString`] or [`SourceDiagnostic`] with severity `Error`.
#[macro_export]
#[doc(hidden)]
macro_rules! __error {
@@ -52,7 +52,19 @@ macro_rules! __error {
};
($span:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {
- $crate::diag::SourceError::new(
+ $crate::diag::SourceDiagnostic::error(
+ $span,
+ $crate::diag::eco_format!($fmt, $($arg),*),
+ )
+ };
+}
+
+/// Construct a [`SourceDiagnostic`] with severity `Warning`.
+#[macro_export]
+#[doc(hidden)]
+macro_rules! __warning {
+ ($span:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {
+ $crate::diag::SourceDiagnostic::warning(
$span,
$crate::diag::eco_format!($fmt, $($arg),*),
)
@@ -61,33 +73,58 @@ macro_rules! __error {
#[doc(inline)]
pub use crate::__error as error;
+#[doc(inline)]
+pub use crate::__warning as warning;
#[doc(hidden)]
pub use ecow::{eco_format, EcoString};
/// A result that can carry multiple source errors.
-pub type SourceResult<T> = Result<T, Box<Vec<SourceError>>>;
+pub type SourceResult<T> = Result<T, Box<Vec<SourceDiagnostic>>>;
-/// An error in a source file.
+/// An error or warning in a source file.
///
/// The contained spans will only be detached if any of the input source files
/// were detached.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
-pub struct SourceError {
- /// The span of the erroneous node in the source code.
+pub struct SourceDiagnostic {
+ /// Whether the diagnostic is an error or a warning.
+ pub severity: Severity,
+ /// The span of the relevant node in the source code.
pub span: Span,
/// A diagnostic message describing the problem.
pub message: EcoString,
- /// The trace of function calls leading to the error.
+ /// The trace of function calls leading to the problem.
pub trace: Vec<Spanned<Tracepoint>>,
- /// Additonal hints to the user, indicating how this error could be avoided
+ /// Additonal hints to the user, indicating how this problem could be avoided
/// or worked around.
pub hints: Vec<EcoString>,
}
-impl SourceError {
+/// The severity of a [`SourceDiagnostic`].
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
+pub enum Severity {
+ /// A fatal error.
+ Error,
+ /// A non-fatal warning.
+ Warning,
+}
+
+impl SourceDiagnostic {
/// Create a new, bare error.
- pub fn new(span: Span, message: impl Into<EcoString>) -> Self {
+ pub fn error(span: Span, message: impl Into<EcoString>) -> Self {
+ Self {
+ severity: Severity::Error,
+ span,
+ trace: vec![],
+ message: message.into(),
+ hints: vec![],
+ }
+ }
+
+ /// Create a new, bare warning.
+ pub fn warning(span: Span, message: impl Into<EcoString>) -> Self {
Self {
+ severity: Severity::Warning,
span,
trace: vec![],
message: message.into(),
@@ -95,16 +132,23 @@ impl SourceError {
}
}
- /// Adds user-facing hints to the error.
+ /// Adds a single hint to the diagnostic.
+ pub fn with_hint(mut self, hint: EcoString) -> Self {
+ self.hints.push(hint);
+ self
+ }
+
+ /// Adds user-facing hints to the diagnostic.
pub fn with_hints(mut self, hints: impl IntoIterator<Item = EcoString>) -> Self {
self.hints.extend(hints);
self
}
}
-impl From<SyntaxError> for SourceError {
+impl From<SyntaxError> for SourceDiagnostic {
fn from(error: SyntaxError) -> Self {
Self {
+ severity: Severity::Error,
span: error.span,
message: error.message,
trace: vec![],
@@ -113,7 +157,7 @@ impl From<SyntaxError> for SourceError {
}
}
-/// A part of an error's [trace](SourceError::trace).
+/// A part of a diagnostic's [trace](SourceDiagnostic::trace).
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Tracepoint {
/// A function call.
@@ -194,7 +238,7 @@ where
S: Into<EcoString>,
{
fn at(self, span: Span) -> SourceResult<T> {
- self.map_err(|message| Box::new(vec![SourceError::new(span, message)]))
+ self.map_err(|message| Box::new(vec![SourceDiagnostic::error(span, message)]))
}
}
@@ -214,7 +258,9 @@ pub struct HintedString {
impl<T> At<T> for Result<T, HintedString> {
fn at(self, span: Span) -> SourceResult<T> {
self.map_err(|diags| {
- Box::new(vec![SourceError::new(span, diags.message).with_hints(diags.hints)])
+ Box::new(vec![
+ SourceDiagnostic::error(span, diags.message).with_hints(diags.hints)
+ ])
})
}
}
diff --git a/crates/typst/src/eval/mod.rs b/crates/typst/src/eval/mod.rs
index f72eeaa4..b4048e1b 100644
--- a/crates/typst/src/eval/mod.rs
+++ b/crates/typst/src/eval/mod.rs
@@ -24,6 +24,7 @@ mod none;
pub mod ops;
mod scope;
mod symbol;
+mod tracer;
#[doc(hidden)]
pub use {
@@ -53,6 +54,7 @@ pub use self::none::NoneValue;
pub use self::scope::{Scope, Scopes};
pub use self::str::{format_str, Regex, Str};
pub use self::symbol::Symbol;
+pub use self::tracer::Tracer;
pub use self::value::{Dynamic, Type, Value};
use std::collections::HashSet;
@@ -66,7 +68,8 @@ use unicode_segmentation::UnicodeSegmentation;
use self::func::{CapturesVisitor, Closure};
use crate::diag::{
- bail, error, At, FileError, SourceError, SourceResult, StrResult, Trace, Tracepoint,
+ bail, error, warning, At, FileError, SourceDiagnostic, SourceResult, StrResult,
+ Trace, Tracepoint,
};
use crate::model::{
Content, DelayedErrors, Introspector, Label, Locator, Recipe, ShowableSelector,
@@ -292,7 +295,7 @@ pub enum FlowEvent {
impl FlowEvent {
/// Return an error stating that this control flow is forbidden.
- pub fn forbidden(&self) -> SourceError {
+ pub fn forbidden(&self) -> SourceDiagnostic {
match *self {
Self::Break(span) => {
error!(span, "cannot break outside of loop")
@@ -351,47 +354,6 @@ impl<'a> Route<'a> {
}
}
-/// Traces which values existed for an expression at a span.
-#[derive(Default, Clone)]
-pub struct Tracer {
- span: Option<Span>,
- values: Vec<Value>,
-}
-
-impl Tracer {
- /// The maximum number of traced items.
- pub const MAX: usize = 10;
-
- /// Create a new tracer, possibly with a span under inspection.
- pub fn new(span: Option<Span>) -> Self {
- Self { span, values: vec![] }
- }
-
- /// Get the traced values.
- pub fn finish(self) -> Vec<Value> {
- self.values
- }
-}
-
-#[comemo::track]
-impl Tracer {
- /// The traced span if it is part of the given source file.
- fn span(&self, id: FileId) -> Option<Span> {
- if self.span.map(Span::id) == Some(id) {
- self.span
- } else {
- None
- }
- }
-
- /// Trace a value for the span.
- fn trace(&mut self, v: Value) {
- if self.values.len() < Self::MAX {
- self.values.push(v);
- }
- }
-}
-
/// Evaluate an expression.
pub(super) trait Eval {
/// The output of evaluating the expression.
@@ -616,7 +578,18 @@ impl Eval for ast::Strong {
#[tracing::instrument(name = "Strong::eval", skip_all)]
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items.strong)(self.body().eval(vm)?))
+ let body = self.body();
+ if body.exprs().next().is_none() {
+ vm.vt
+ .tracer
+ .warn(warning!(self.span(), "no text within stars").with_hint(
+ EcoString::from(
+ "using multiple consecutive stars (e.g. **) has no additional effect",
+ ),
+ ));
+ }
+
+ Ok((vm.items.strong)(body.eval(vm)?))
}
}
diff --git a/crates/typst/src/eval/tracer.rs b/crates/typst/src/eval/tracer.rs
new file mode 100644
index 00000000..0be6c189
--- /dev/null
+++ b/crates/typst/src/eval/tracer.rs
@@ -0,0 +1,70 @@
+use std::collections::HashSet;
+
+use ecow::{eco_vec, EcoVec};
+
+use super::Value;
+use crate::diag::SourceDiagnostic;
+use crate::syntax::{FileId, Span};
+use crate::util::hash128;
+
+/// Traces warnings and which values existed for an expression at a span.
+#[derive(Default, Clone)]
+pub struct Tracer {
+ span: Option<Span>,
+ values: EcoVec<Value>,
+ warnings: EcoVec<SourceDiagnostic>,
+ warnings_set: HashSet<u128>,
+}
+
+impl Tracer {
+ /// The maximum number of traced items.
+ pub const MAX: usize = 10;
+
+ /// Create a new tracer, possibly with a span under inspection.
+ pub fn new(span: Option<Span>) -> Self {
+ Self {
+ span,
+ values: eco_vec![],
+ warnings: eco_vec![],
+ warnings_set: HashSet::new(),
+ }
+ }
+
+ /// Get the traced values.
+ pub fn values(self) -> EcoVec<Value> {
+ self.values
+ }
+
+ /// Get the stored warnings.
+ pub fn warnings(self) -> EcoVec<SourceDiagnostic> {
+ self.warnings
+ }
+}
+
+#[comemo::track]
+impl Tracer {
+ /// The traced span if it is part of the given source file.
+ pub fn span(&self, id: FileId) -> Option<Span> {
+ if self.span.map(Span::id) == Some(id) {
+ self.span
+ } else {
+ None
+ }
+ }
+
+ /// Trace a value for the span.
+ pub fn trace(&mut self, v: Value) {
+ if self.values.len() < Self::MAX {
+ self.values.push(v);
+ }
+ }
+
+ /// Add a warning.
+ pub fn warn(&mut self, warning: SourceDiagnostic) {
+ // Check if warning is a duplicate.
+ let hash = hash128(&(&warning.span, &warning.message));
+ if self.warnings_set.insert(hash) {
+ self.warnings.push(warning);
+ }
+ }
+}
diff --git a/crates/typst/src/ide/analyze.rs b/crates/typst/src/ide/analyze.rs
index dad466c1..c143720a 100644
--- a/crates/typst/src/ide/analyze.rs
+++ b/crates/typst/src/ide/analyze.rs
@@ -1,5 +1,5 @@
use comemo::Track;
-use ecow::EcoString;
+use ecow::{eco_vec, EcoString, EcoVec};
use crate::doc::Frame;
use crate::eval::{eval, Module, Route, Tracer, Value};
@@ -8,18 +8,18 @@ use crate::syntax::{ast, LinkedNode, Source, SyntaxKind};
use crate::World;
/// Try to determine a set of possible values for an expression.
-pub fn analyze_expr(world: &(dyn World + 'static), node: &LinkedNode) -> Vec<Value> {
+pub fn analyze_expr(world: &dyn World, node: &LinkedNode) -> EcoVec<Value> {
match node.cast::<ast::Expr>() {
- Some(ast::Expr::None(_)) => vec![Value::None],
- Some(ast::Expr::Auto(_)) => vec![Value::Auto],
- Some(ast::Expr::Bool(v)) => vec![Value::Bool(v.get())],
- Some(ast::Expr::Int(v)) => vec![Value::Int(v.get())],
- Some(ast::Expr::Float(v)) => vec![Value::Float(v.get())],
- Some(ast::Expr::Numeric(v)) => vec![Value::numeric(v.get())],
- Some(ast::Expr::Str(v)) => vec![Value::Str(v.get().into())],
+ Some(ast::Expr::None(_)) => eco_vec![Value::None],
+ Some(ast::Expr::Auto(_)) => eco_vec![Value::Auto],
+ Some(ast::Expr::Bool(v)) => eco_vec![Value::Bool(v.get())],
+ Some(ast::Expr::Int(v)) => eco_vec![Value::Int(v.get())],
+ Some(ast::Expr::Float(v)) => eco_vec![Value::Float(v.get())],
+ Some(ast::Expr::Numeric(v)) => eco_vec![Value::numeric(v.get())],
+ Some(ast::Expr::Str(v)) => eco_vec![Value::Str(v.get().into())],
Some(ast::Expr::FieldAccess(access)) => {
- let Some(child) = node.children().next() else { return vec![] };
+ let Some(child) = node.children().next() else { return eco_vec![] };
analyze_expr(world, &child)
.into_iter()
.filter_map(|target| target.field(&access.field()).ok())
@@ -33,36 +33,17 @@ pub fn analyze_expr(world: &(dyn World + 'static), node: &LinkedNode) -> Vec<Val
}
}
- let route = Route::default();
let mut tracer = Tracer::new(Some(node.span()));
- typst::eval::eval(
- world.track(),
- route.track(),
- tracer.track_mut(),
- &world.main(),
- )
- .and_then(|module| {
- typst::model::typeset(
- world.track(),
- tracer.track_mut(),
- &module.content(),
- )
- })
- .ok();
-
- tracer.finish()
+ crate::compile(world, &mut tracer).ok();
+ tracer.values()
}
- _ => vec![],
+ _ => eco_vec![],
}
}
/// Try to load a module from the current source file.
-pub fn analyze_import(
- world: &(dyn World + 'static),
- source: &Source,
- path: &str,
-) -> Option<Module> {
+pub fn analyze_import(world: &dyn World, source: &Source, path: &str) -> Option<Module> {
let route = Route::default();
let mut tracer = Tracer::default();
let id = source.id().join(path).ok()?;
@@ -77,7 +58,7 @@ pub fn analyze_import(
/// - A split offset: All labels before this offset belong to nodes, all after
/// belong to a bibliography.
pub fn analyze_labels(
- world: &(dyn World + 'static),
+ world: &dyn World,
frames: &[Frame],
) -> (Vec<(Label, Option<EcoString>)>, usize) {
let mut output = vec![];
diff --git a/crates/typst/src/lib.rs b/crates/typst/src/lib.rs
index 861cb853..3365bbaa 100644
--- a/crates/typst/src/lib.rs
+++ b/crates/typst/src/lib.rs
@@ -66,10 +66,9 @@ use crate::syntax::{FileId, PackageSpec, Source, Span};
use crate::util::Bytes;
/// Compile a source file into a fully layouted document.
-#[tracing::instrument(skip(world))]
-pub fn compile(world: &dyn World) -> SourceResult<Document> {
+#[tracing::instrument(skip_all)]
+pub fn compile(world: &dyn World, tracer: &mut Tracer) -> SourceResult<Document> {
let route = Route::default();
- let mut tracer = Tracer::default();
// Call `track` just once to keep comemo's ID stable.
let world = world.track();
@@ -83,7 +82,7 @@ pub fn compile(world: &dyn World) -> SourceResult<Document> {
&world.main(),
)?;
- // Typeset the module's contents.
+ // Typeset it.
model::typeset(world, tracer, &module.content())
}
diff --git a/crates/typst/src/model/mod.rs b/crates/typst/src/model/mod.rs
index ee940236..cab1f71f 100644
--- a/crates/typst/src/model/mod.rs
+++ b/crates/typst/src/model/mod.rs
@@ -28,7 +28,7 @@ use std::mem::ManuallyDrop;
use comemo::{Track, Tracked, TrackedMut, Validate};
-use crate::diag::{SourceError, SourceResult};
+use crate::diag::{SourceDiagnostic, SourceResult};
use crate::doc::Document;
use crate::eval::Tracer;
use crate::World;
@@ -137,12 +137,12 @@ impl Vt<'_> {
/// Holds delayed errors.
#[derive(Default, Clone)]
-pub struct DelayedErrors(Vec<SourceError>);
+pub struct DelayedErrors(Vec<SourceDiagnostic>);
#[comemo::track]
impl DelayedErrors {
/// Push a delayed error.
- fn push(&mut self, error: SourceError) {
+ fn push(&mut self, error: SourceDiagnostic) {
self.0.push(error);
}
}
diff --git a/tests/src/benches.rs b/tests/src/benches.rs
index 8e70ffd7..524fda19 100644
--- a/tests/src/benches.rs
+++ b/tests/src/benches.rs
@@ -1,7 +1,7 @@
use comemo::{Prehashed, Track, Tracked};
use iai::{black_box, main, Iai};
use typst::diag::FileResult;
-use typst::eval::{Datetime, Library};
+use typst::eval::{Datetime, Library, Tracer};
use typst::font::{Font, FontBook};
use typst::geom::Color;
use typst::syntax::{FileId, Source};
@@ -83,12 +83,14 @@ fn bench_typeset(iai: &mut Iai) {
fn bench_compile(iai: &mut Iai) {
let world = BenchWorld::new();
- iai.run(|| typst::compile(&world));
+ let mut tracer = Tracer::default();
+ iai.run(|| typst::compile(&world, &mut tracer));
}
fn bench_render(iai: &mut Iai) {
let world = BenchWorld::new();
- let document = typst::compile(&world).unwrap();
+ let mut tracer = Tracer::default();
+ let document = typst::compile(&world, &mut tracer).unwrap();
iai.run(|| typst::export::render(&document.pages[0], 1.0, Color::WHITE))
}
diff --git a/tests/src/tests.rs b/tests/src/tests.rs
index ee9ae9f9..a8cf25f7 100644
--- a/tests/src/tests.rs
+++ b/tests/src/tests.rs
@@ -20,9 +20,9 @@ use tiny_skia as sk;
use unscanny::Scanner;
use walkdir::WalkDir;
-use typst::diag::{bail, FileError, FileResult, StrResult};
+use typst::diag::{bail, FileError, FileResult, Severity, StrResult};
use typst::doc::{Document, Frame, FrameItem, Meta};
-use typst::eval::{eco_format, func, Datetime, Library, NoneValue, Value};
+use typst::eval::{eco_format, func, Datetime, Library, NoneValue, Tracer, Value};
use typst::font::{Font, FontBook};
use typst::geom::{Abs, Color, RgbaColor, Smart};
use typst::syntax::{FileId, Source, Span, SyntaxNode};
@@ -514,51 +514,63 @@ fn test_part(
let world = (world as &dyn World).track();
let route = typst::eval::Route::default();
let mut tracer = typst::eval::Tracer::default();
+
let module =
typst::eval::eval(world, route.track(), tracer.track_mut(), &source).unwrap();
writeln!(output, "Model:\n{:#?}\n", module.content()).unwrap();
}
- let (mut frames, errors) = match typst::compile(world) {
- Ok(document) => (document.pages, vec![]),
- Err(errors) => (vec![], *errors),
+ let mut tracer = Tracer::default();
+
+ let (mut frames, diagnostics) = match typst::compile(world, &mut tracer) {
+ Ok(document) => (document.pages, tracer.warnings()),
+ Err(errors) => {
+ let mut warnings = tracer.warnings();
+ warnings.extend(*errors);
+ (vec![], warnings)
+ }
};
- // Don't retain frames if we don't wanna compare with reference images.
+ // Don't retain frames if we don't want to compare with reference images.
if !compare_ref {
frames.clear();
}
- // Map errors to range and message format, discard traces and errors from
+ // Map diagnostics to range and message format, discard traces and errors from
// other files, collect hints.
//
// This has one caveat: due to the format of the expected hints, we can not
- // verify if a hint belongs to a error or not. That should be irrelevant
+ // verify if a hint belongs to a diagnostic or not. That should be irrelevant
// however, as the line of the hint is still verified.
- let actual_errors_and_hints: HashSet<UserOutput> = errors
+ let actual_diagnostics: HashSet<UserOutput> = diagnostics
.into_iter()
- .inspect(|error| assert!(!error.span.is_detached()))
- .filter(|error| error.span.id() == source.id())
- .flat_map(|error| {
- let range = world.range(error.span);
- let output_error =
- UserOutput::Error(range.clone(), error.message.replace('\\', "/"));
- let hints = error
+ .inspect(|diagnostic| assert!(!diagnostic.span.is_detached()))
+ .filter(|diagnostic| diagnostic.span.id() == source.id())
+ .flat_map(|diagnostic| {
+ let range = world.range(diagnostic.span);
+ let message = diagnostic.message.replace('\\', "/");
+ let output = match diagnostic.severity {
+ Severity::Error => UserOutput::Error(range.clone(), message),
+ Severity::Warning => UserOutput::Warning(range.clone(), message),
+ };
+
+ let hints = diagnostic
.hints
.iter()
.filter(|_| validate_hints) // No unexpected hints should be verified if disabled.
.map(|hint| UserOutput::Hint(range.clone(), hint.to_string()));
- iter::once(output_error).chain(hints).collect::<Vec<_>>()
+
+ iter::once(output).chain(hints).collect::<Vec<_>>()
})
.collect();
// Basically symmetric_difference, but we need to know where an item is coming from.
- let mut unexpected_outputs = actual_errors_and_hints
+ let mut unexpected_outputs = actual_diagnostics
.difference(&metadata.invariants)
.collect::<Vec<_>>();
let mut missing_outputs = metadata
.invariants
- .difference(&actual_errors_and_hints)
+ .difference(&actual_diagnostics)
.collect::<Vec<_>>();
unexpected_outputs.sort_by_key(|&o| o.start());
@@ -592,6 +604,7 @@ fn print_user_output(
) {
let (range, message) = match &user_output {
UserOutput::Error(r, m) => (r, m),
+ UserOutput::Warning(r, m) => (r, m),
UserOutput::Hint(r, m) => (r, m),
};
@@ -601,6 +614,7 @@ fn print_user_output(
let end_col = 1 + source.byte_to_column(range.end).unwrap();
let kind = match user_output {
UserOutput::Error(_, _) => "Error",
+ UserOutput::Warning(_, _) => "Warning",
UserOutput::Hint(_, _) => "Hint",
};
writeln!(output, "{kind}: {start_line}:{start_col}-{end_line}:{end_col}: {message}")
@@ -620,6 +634,7 @@ struct TestPartMetadata {
#[derive(PartialEq, Eq, Debug, Hash)]
enum UserOutput {
Error(Range<usize>, String),
+ Warning(Range<usize>, String),
Hint(Range<usize>, String),
}
@@ -627,6 +642,7 @@ impl UserOutput {
fn start(&self) -> usize {
match self {
UserOutput::Error(r, _) => r.start,
+ UserOutput::Warning(r, _) => r.start,
UserOutput::Hint(r, _) => r.start,
}
}
@@ -635,6 +651,10 @@ impl UserOutput {
UserOutput::Error(range, message)
}
+ fn warning(range: Range<usize>, message: String) -> UserOutput {
+ UserOutput::Warning(range, message)
+ }
+
fn hint(range: Range<usize>, message: String) -> UserOutput {
UserOutput::Hint(range, message)
}
@@ -666,12 +686,18 @@ fn parse_part_metadata(source: &Source) -> TestPartMetadata {
};
let error_factory: fn(Range<usize>, String) -> UserOutput = UserOutput::error;
+ let warning_factory: fn(Range<usize>, String) -> UserOutput = UserOutput::warning;
let hint_factory: fn(Range<usize>, String) -> UserOutput = UserOutput::hint;
let error_metadata = get_metadata(line, "Error").map(|s| (s, error_factory));
+ let get_warning_metadata =
+ || get_metadata(line, "Warning").map(|s| (s, warning_factory));
let get_hint_metadata = || get_metadata(line, "Hint").map(|s| (s, hint_factory));
- if let Some((expectation, factory)) = error_metadata.or_else(get_hint_metadata) {
+ if let Some((expectation, factory)) = error_metadata
+ .or_else(get_warning_metadata)
+ .or_else(get_hint_metadata)
+ {
let mut s = Scanner::new(expectation);
let start = pos(&mut s);
let end = if s.eat_if('-') { pos(&mut s) } else { start };
diff --git a/tests/typ/lint/markup.typ b/tests/typ/lint/markup.typ
new file mode 100644
index 00000000..1cb44658
--- /dev/null
+++ b/tests/typ/lint/markup.typ
@@ -0,0 +1,13 @@
+/// Test markup lints.
+// Ref: false
+
+---
+// Warning: 1-3 no text within stars
+// Hint: 1-3 using multiple consecutive stars (e.g. **) has no additional effect
+**
+---
+// Warning: 1-3 no text within stars
+// Hint: 1-3 using multiple consecutive stars (e.g. **) has no additional effect
+// Warning: 11-13 no text within stars
+// Hint: 11-13 using multiple consecutive stars (e.g. **) has no additional effect
+**not bold**