From d4242ff8c1086842a6d7c601d4ffe32feb6f9bc2 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 17 Sep 2024 11:23:35 +0200 Subject: [PATCH] Turn unresolved references and citations into warnings --- crates/typst/src/model/cite.rs | 26 ++++++++++- crates/typst/src/model/reference.rs | 52 +++++++++++++++++----- tests/ref/cite-missing.png | Bin 0 -> 1881 bytes tests/ref/label-multiple-ignored-warn.png | Bin 0 -> 894 bytes tests/ref/ref-label-complex-missing.png | Bin 0 -> 1291 bytes tests/ref/ref-label-missing.png | Bin 0 -> 423 bytes tests/suite/foundations/label.typ | 14 +++--- tests/suite/model/cite.typ | 11 +++++ tests/suite/model/ref.typ | 8 +++- 9 files changed, 89 insertions(+), 22 deletions(-) create mode 100644 tests/ref/cite-missing.png create mode 100644 tests/ref/label-multiple-ignored-warn.png create mode 100644 tests/ref/ref-label-complex-missing.png create mode 100644 tests/ref/ref-label-missing.png diff --git a/crates/typst/src/model/cite.rs b/crates/typst/src/model/cite.rs index e156e0ec9..fea4fcea0 100644 --- a/crates/typst/src/model/cite.rs +++ b/crates/typst/src/model/cite.rs @@ -1,3 +1,5 @@ +use ecow::eco_format; + use crate::diag::{error, At, HintedString, SourceResult}; use crate::engine::Engine; use crate::foundations::{ @@ -5,7 +7,7 @@ use crate::foundations::{ }; use crate::introspection::Locatable; use crate::model::bibliography::Works; -use crate::model::CslStyle; +use crate::model::{unresolved_reference, BibliographyElem, CslStyle}; use crate::text::{Lang, Region, TextElem}; /// Cite a work from the bibliography. @@ -40,7 +42,7 @@ use crate::text::{Lang, Region, TextElem}; /// This function indirectly has dedicated syntax. [References]($ref) can be /// used to cite works from the bibliography. The label then corresponds to the /// citation key. -#[elem(Synthesize)] +#[elem(Synthesize, Show)] pub struct CiteElem { /// The citation key that identifies the entry in the bibliography that /// shall be cited, as a label. @@ -117,6 +119,26 @@ impl Synthesize for Packed { } } +impl Show for Packed { + #[typst_macros::time(name = "cite", span = self.span())] + fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult { + if !BibliographyElem::has(engine, self.key) { + return Ok(unresolved_reference( + engine, + eco_format!( + "key `{}` does not exist in the bibliography", + self.key.as_str() + ), + "cite", + self.key, + self.span(), + )); + } + + Ok(self.clone().pack()) + } +} + cast! { CiteElem, v: Content => v.unpack::().map_err(|_| "expected citation")?, diff --git a/crates/typst/src/model/reference.rs b/crates/typst/src/model/reference.rs index 8194eec53..33ae14806 100644 --- a/crates/typst/src/model/reference.rs +++ b/crates/typst/src/model/reference.rs @@ -1,18 +1,20 @@ use comemo::Track; -use ecow::eco_format; +use ecow::{eco_format, EcoString}; -use crate::diag::{bail, At, Hint, SourceResult}; +use crate::diag::{bail, At, Hint, SourceDiagnostic, SourceResult}; use crate::engine::Engine; use crate::foundations::{ - cast, elem, Content, Context, Func, IntoValue, Label, NativeElement, Packed, Show, - Smart, StyleChain, Synthesize, + cast, elem, Content, Context, Func, IntoValue, Label, NativeElement, Packed, Repr, + Show, Smart, StyleChain, Synthesize, }; -use crate::introspection::{Counter, Locatable}; +use crate::introspection::{Counter, Locatable, QueryError}; use crate::math::EquationElem; use crate::model::{ BibliographyElem, CiteElem, Destination, Figurable, FootnoteElem, Numbering, }; -use crate::text::TextElem; +use crate::syntax::{is_valid_label_literal, Span}; +use crate::text::{RawContent, RawElem, TextElem}; +use crate::visualize::Color; /// A reference to a label or bibliography. /// @@ -163,22 +165,25 @@ impl Synthesize for Packed { impl Show for Packed { #[typst_macros::time(name = "ref", span = self.span())] fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult { - let target = *self.target(); - let elem = engine.introspector.query_label(target); let span = self.span(); + let result = engine.introspector.query_label(self.target); - if BibliographyElem::has(engine, target) { - if elem.is_ok() { + if BibliographyElem::has(engine, self.target) { + if result.is_ok() { bail!(span, "label occurs in the document and its bibliography"); } return Ok(to_citation(self, engine, styles)?.pack().spanned(span)); } - let elem = elem.at(span)?; + if let Err(error @ QueryError::MissingLabel(_)) = result { + return Ok(unresolved_reference(engine, error, "ref", self.target, span)); + } + + let elem = result.at(span)?; if let Some(footnote) = elem.to_packed::() { - return Ok(footnote.into_ref(target).pack().spanned(span)); + return Ok(footnote.into_ref(self.target).pack().spanned(span)); } let elem = elem.clone(); @@ -305,3 +310,26 @@ pub trait Refable { /// Returns the numbering of this element. fn numbering(&self) -> Option<&Numbering>; } + +/// Generates a warning for an unresolved reference and returns placeholder +/// content. +pub(crate) fn unresolved_reference( + engine: &mut Engine, + message: impl Into, + func: &str, + target: Label, + span: Span, +) -> Content { + engine.sink.warn(SourceDiagnostic::warning(span, message)); + + let text = if is_valid_label_literal(target.as_str()) { + eco_format!("@{}", target.as_str()) + } else { + eco_format!("#{func}(label({}))", target.as_str().repr()) + }; + + return RawElem::new(RawContent::Text(text)) + .pack() + .spanned(span) + .styled(TextElem::set_fill(Color::RED.into())); +} diff --git a/tests/ref/cite-missing.png b/tests/ref/cite-missing.png new file mode 100644 index 0000000000000000000000000000000000000000..beb97f5877ebde366af63b899e3327f9bb1ff5d5 GIT binary patch literal 1881 zcmV-f2d4OmP)P0xg000LdNklcoJ~V0i*rt!h#Ku~!BHlm{@iG)}aCAfzhpT{MRW5=lQbippT9FD~ z5DGGb!*#fp%Ycjma#coPb$waMVVoJ%CY&a=`^(8>&zybsS^Kxw-e=CYKO&`nMIR{y z3bX>Pq(Cds3bc{}tw1Y%XrQaBtJBld>+0&rOY`&dg@uJlNlCM_v-GAs9#3j&YEDiL z7syL?yFDW#qpGUve*^mUYkF^ujZf{~6H`=d?d~0xqxf>ER$jGwU428#%a?OneJ(qi zyT18$l~ibSbmZg}KWp%*3vtnVf%)LM|Fx^f6yhD=u{ddnrY$%y;ISV{}lAy zs#?a>-qCM5ocQTyUud9%w}uA4i1SNA4li2QNB_{ zg}KCv*v5AS@sX#*+Pl*?FUCft4Cr!6{yFI+I<^9!ao8H$*Z;VUXF~wcnaSkwfb7LQ z#93wqcl5|Yy*=P=ipDhUboysB#%9Mx)R9nMy`ug;G+6skFVGn9M-PzHo;ecGRTV^p z4!B2X+Xl`8EG%a-e8X*Pp^!~-7HJ}&9d;6}1n8!g?uf|fHfJA~{Xrx0-fp_~D;JTE z=T0GJiKnlZWJ%l;N>BjMs41G9pnYNJc!CKNS})LPi8MV$M-L+IbPMj6atupBSTN5@ zWJG-p$y^f|4XDqWo~FnhG(N_Ok0BAzt<7%%-O)Ap@h6}AfOfl|I~++S$9i6>HL&HHqK?E_S`Tl|7&B5EHLg>y2uCpT~{BGp~{@UE) z+#R`pYHF55oldN?teI&^077H_?CGmhRsi2_1 z=Y_>)vvE7+<>l(lP`!dHDbNbEk^-$jE6_>`v;wW93uw3j9uHh3e?5Oj9qsgff*U2i zM{5&ZDx~gC3aFap-N>;~z7U3@mtfUu*1mfLuGY(0@)InC)@HQ861TR!74@~3UeBC% z7G{pOZ1wts9(@ioI`<5$7C)hrO;1P@ZvreYv@-~w4r(A=2f^M`Sf@6m$ zGzj*U*j?sWI+=vBvz5&`n2jvI9Tqr$<^y~={4+_Y*uCCf6+mnGl4yn*>#z%Gcx^JwjDY4Th#3ir-O?tshNE0y8hlc%|OzR2a2`uJtjcLJJSZ>+^e1GmPw zg}W>r+#uft!H9-km?(bY3;A}O?DDu1akFCcV~j86%0mTye?S>_X+_ND6GwCe4YJNw z47;XVrM#FR#biJKWD+*Fw8`7lh}5gEm3?TgiTGNYNW|r7AQK@&Gn3^mn9+G>WgC}A z4xNvWlP4!%F72Q}QC$sg`gE8?=2)x@e!`H5yzls890jg_J{u2);)@{eD|7^>ydI>u z`v>zsx~l`>uDI-vF;punYYBF|G+ioGFD&ZKP)UJSpcQB(1zLetQlJ&+|Em55&utU= TI>27d00000NkvXXu0mjfMfZ;$ literal 0 HcmV?d00001 diff --git a/tests/ref/label-multiple-ignored-warn.png b/tests/ref/label-multiple-ignored-warn.png new file mode 100644 index 0000000000000000000000000000000000000000..f623f3a723e9f9e4b99f25f05a0a5f39452e3457 GIT binary patch literal 894 zcmV-^1A+XBP) z_4WS#{s932udlCxfq|KsnF0a={QUg;`}^kR=980?b8~ZTZEbRLa`5o*>FMb%E-v!& z^8dcNy1Kfms;WanLv?j^85tR%pr9Na9HyqGYHDhHe0(D#BYu8_qwYBW*><0%27Z(>87#I%^4?jOY+1c4*Vq#lcTRuKMz`(#| zWo6CH&EVkRQBhIb+uQ%Pvj32Y|7l?V_xJzk=KqzC|N8m=^z;9OegFOZ|KsBSu&e*D ztpCNq|Fp3G^YZ_Pfd9tB|H#Gv@$vtIeE*q~|Mm3$*VX^~`~SVU|CEmZ+}i(vdH;%p z|If_-^6~$oo&UkT|Bs0OUsnH4ME{?e|EZ+^my!RLkN^Gs|B{STQ&VPUW)u_@rKP0? z1_sg5(cRtMSXfx!-{0Zk;m60vsi~=Wcz7Nj9$H#j?(Xj5;^Oo3^Ou*Go}QjeOiTy} z2&1E;Z*OmpkB?bdS%rm#9UUD_O-&&oAp-*gN=i!S=jTO5MFj-~1Ox=a!ou>KeL)?7aVMlv!FiwTJq!zJ82X|PR3rc=E{ zXFv)?#xJkHheCj7a>EtfV)f<~On7@J0)sHWpg6yONR6WFQ~m2Rg}xY{6zdz87w>*+ zJ77Z+q&EX}dV@g^z}*dS__~pV@AKBC$d^0ES-+~{?q2+Z{OXdPzSg=jFUU;Y*wn0U zYpE!J%qHp^O4RLLm4lGkKy6vEx}&pV2r}!hDK9N7niw8|+`LA|#`)joIL`Wg0(_TE UJ1PPFMgRZ+07*qoM6N<$g5JE_n*aa+ literal 0 HcmV?d00001 diff --git a/tests/ref/ref-label-complex-missing.png b/tests/ref/ref-label-complex-missing.png new file mode 100644 index 0000000000000000000000000000000000000000..933721a2e0d28f74537576e3cf10a8b64a1e08f7 GIT binary patch literal 1291 zcmWNRi8mAo0LDi{%cy;|=~O9fy)9c(ZN0T=E0HX*OQDowP`N61?js>445J(|jGM_I ze(!zX?;rTy7pkYLxt_FxL?95>Yh6(Pch$7j?2sg`wne_7 z6M?WsUhf}+^H^nJ0dsR$TEhGsI@++Zg0d2Pn}%oxKYn0(8bbn9yu#F1L_NaDFlN4C zP=LrVM2BN|2$`u6@F5rgBN;7?2=;}qJ4#<5Cj+?|D98ovF)B-uK}UTJB7(vD03H|Z z@6gtQ^c2t{AsoXkGvsH%<1RQI=;EOH4N6!@NkDxq;$u*dhi6Za{1pBkFgF6@86rc` z`w92&!R7|&3Ak>6)+Rg-L0%?0-^0@x&2RCt2%mVcw}Q=e4D_Lhi9jzDFBfDp z;C~-sLHIfe_d8%_V|WlIdY~sFIUZKm;OBwj0=!^>+ldcd82gNfhxj}K7khjjh42f! zT`(@hmoYrgful8?Y(Wi&tp$pngWr$-KJ@kAZ5;~p@sW$TC|H_+%R%g8aN1#KiPR({ z#lh79SM_nn2IVi2m4+a1G&bOU8(8^x-~(y|?%jrq18&|xVjTK=@sx`22XMcO7z(P( zQBnv`CsbG9O&zMr(C`{{Y`iK)LM(c^VQmU_1s?igdI}TcuqPul0EskAPN23L0iNjo zh|yt$1>*ZGL?SFN!Oan~A}lUqVIIrN`0-tv{q*kYS{PdDXAIoCC%OzTDd=zWAl z`E)c}q|&SuoM)>Dl3Kc~vn`Gm7FSXV*3+lrswCPg15KqaD<9l1qkmXTFsO2beApjdvW~XUm1nlDQbFIyH8z^HOqH|MjTa91lk%(rgts~4xQ#q>L7%v^( z8ujxvj)GZlyzfry4EhP;+Y@pP%6%u8+1a~y@&z=RM*T=uH%a(X>|olfvP`DANiOHs z-g4=W&oqtnv*#sLo@?;At?}^%lI~HH zCBeKYsgn1GXA$+M4~wD`ZPQF=s2`hQcW(@`E$18@kz6n)jTda<_HCNT|2j{MIqgy& ztF*&!)V{z)w%@BnEM8RK;jp!scy_T?sb{NefR^~WKzg2{wUQ>cEO`5f-_XVh;wDDA zn8dm87ly|T%cKf@*Sb2$EA^TsB%58H3{Gs+yBsTM{)-kCXX9wknZqqf+xpOEVIpOm^~FXpPbfu_8l zxO`*w+8e%K!fU|HZ=pl#c)9rDe8(wTt;$rSJm040GS*<8LLry@zx&%P+%49u*G R3n>5q002ovPDHLkV1gZ+{Eq+t literal 0 HcmV?d00001 diff --git a/tests/suite/foundations/label.typ b/tests/suite/foundations/label.typ index af6d23803..f662695a0 100644 --- a/tests/suite/foundations/label.typ +++ b/tests/suite/foundations/label.typ @@ -72,16 +72,16 @@ _Visible_ // Hint: 1-8 only the last label is used, the rest are ignored = Hello -// Warning: 12-19 content labelled multiple times -// Hint: 12-19 only the last label is used, the rest are ignored -#let f = [#block()] +// Warning: 12-26 content labelled multiple times +// Hint: 12-26 only the last label is used, the rest are ignored +#let f = [#metadata(none)] #f -// Warning: 6-13 content labelled multiple times -// Hint: 6-13 only the last label is used, the rest are ignored -#[#[#block()]] +// Warning: 6-20 content labelled multiple times +// Hint: 6-20 only the last label is used, the rest are ignored +#[#[#metadata(none)]] -// Error: 1-3 label `` does not exist in the document +// Warning: 1-3 label `` does not exist in the document @a --- label-unattached-warn --- diff --git a/tests/suite/model/cite.typ b/tests/suite/model/cite.typ index ffbd3b52f..34f91f4ca 100644 --- a/tests/suite/model/cite.typ +++ b/tests/suite/model/cite.typ @@ -49,6 +49,17 @@ A @netwok @arrgh @quark, B. #show bibliography: none #bibliography("/assets/bib/works.bib") +--- cite-missing --- +// Warning: 2-15 key `peter` does not exist in the bibliography +// Warning: 31-37 label `` does not exist in the document +#cite() @netwok @arrgh @extra + +// Warning: 2-20 key `>?&` does not exist in the bibliography +#cite(label(">?&")) + +#show bibliography: none +#bibliography("/assets/bib/works.bib") + --- issue-785-cite-locate --- // Test citation in other introspection. #set page(width: 180pt) diff --git a/tests/suite/model/ref.typ b/tests/suite/model/ref.typ index cc6d6bf11..c8ff80674 100644 --- a/tests/suite/model/ref.typ +++ b/tests/suite/model/ref.typ @@ -10,9 +10,15 @@ See @setup. As seen in @intro, we proceed. --- ref-label-missing --- -// Error: 1-5 label `` does not exist in the document +// Warning: 1-5 label `` does not exist in the document @foo +--- ref-label-complex-missing --- +#set page(width: auto) + +// Warning: 2-28 label `label("is;/"bad%//#")` does not exist in the document +#ref(label("is;\"bad%//#")) + --- ref-label-duplicate --- = First = Second