badness_parser/
semantic.rs1pub mod builder;
10pub mod define;
11pub mod doc;
12pub mod expl3;
13pub mod label;
14pub mod outline;
15pub mod pkgmeta;
16pub mod signature;
17pub mod tikz;
18pub mod xparse;
19
20pub use define::{DefSite, DefSiteKind, scan_definition_sites, scan_definitions};
21pub use doc::{DocAssociation, DocKind, doc_associations};
22pub use label::{
23 CitationRef, ColorDef, ColorDefKind, GlossaryDef, GlossaryDefKind, LabelDef, LabelId, LabelRef,
24 RefCommand, RefId,
25};
26pub use outline::{LabelContext, OutlineItem, OutlineSymbol, label_context, outline};
27pub use pkgmeta::{NeedsFormatDecl, OptionDecl, ProvidesDecl, ProvidesKind};
28pub use signature::{
29 ArgKind, ArgSpec, CommandSig, ContentKind, EnvironmentSig, SignatureDb, Signatures,
30};
31
32use crate::syntax::SyntaxNode;
33
34#[derive(Debug, Default, PartialEq, Eq)]
40pub struct SemanticModel {
41 pub(crate) labels: Vec<LabelDef>,
42 pub(crate) refs: Vec<LabelRef>,
43 pub(crate) citations: Vec<CitationRef>,
44 pub(crate) glossary_defs: Vec<GlossaryDef>,
46 pub(crate) color_defs: Vec<ColorDef>,
49 pub(crate) nocite_all: bool,
53 pub(crate) provides: Option<ProvidesDecl>,
56 pub(crate) needs_format: Option<NeedsFormatDecl>,
58 pub(crate) options: Vec<OptionDecl>,
60}
61
62impl SemanticModel {
63 pub fn build(root: &SyntaxNode) -> Self {
65 builder::build(root)
66 }
67
68 pub fn labels(&self) -> &[LabelDef] {
69 &self.labels
70 }
71
72 pub fn label(&self, id: LabelId) -> &LabelDef {
73 &self.labels[id.0 as usize]
74 }
75
76 pub fn refs(&self) -> &[LabelRef] {
77 &self.refs
78 }
79
80 pub fn citations(&self) -> &[CitationRef] {
82 &self.citations
83 }
84
85 pub fn glossary_defs(&self) -> &[GlossaryDef] {
88 &self.glossary_defs
89 }
90
91 pub fn color_defs(&self) -> &[ColorDef] {
94 &self.color_defs
95 }
96
97 pub fn has_wildcard_nocite(&self) -> bool {
99 self.nocite_all
100 }
101
102 pub fn provides(&self) -> Option<&ProvidesDecl> {
104 self.provides.as_ref()
105 }
106
107 pub fn needs_format(&self) -> Option<&NeedsFormatDecl> {
109 self.needs_format.as_ref()
110 }
111
112 pub fn options(&self) -> &[OptionDecl] {
114 &self.options
115 }
116
117 pub fn reference(&self, id: RefId) -> &LabelRef {
118 &self.refs[id.0 as usize]
119 }
120
121 pub fn unreferenced_labels(&self) -> impl Iterator<Item = LabelId> + '_ {
130 (0..self.labels.len())
131 .map(LabelId::from_index)
132 .filter(move |id| !self.label(*id).referenced)
133 }
134
135 pub fn unresolved_refs(&self) -> impl Iterator<Item = RefId> + '_ {
142 (0..self.refs.len())
143 .map(RefId::from_index)
144 .filter(move |id| !self.reference(*id).resolved)
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use crate::parser::parse;
152
153 fn model_of(src: &str) -> SemanticModel {
154 SemanticModel::build(&SyntaxNode::new_root(parse(src).green))
155 }
156
157 #[test]
158 fn label_creates_def() {
159 let model = model_of("\\label{sec:intro}\n");
160 assert_eq!(model.labels().len(), 1);
161 assert_eq!(model.labels()[0].name, "sec:intro");
162 assert!(!model.labels()[0].referenced);
163 }
164
165 #[test]
166 fn ref_creates_use() {
167 let model = model_of("\\ref{sec:intro}\n");
168 assert_eq!(model.refs().len(), 1);
169 assert_eq!(model.refs()[0].name, "sec:intro");
170 assert_eq!(model.refs()[0].command, RefCommand::Ref);
171 assert!(!model.refs()[0].resolved);
172 }
173
174 #[test]
175 fn label_and_ref_resolve() {
176 let model = model_of("\\label{a}\\ref{a}\n");
177 assert!(model.labels()[0].referenced);
178 assert!(model.refs()[0].resolved);
179 assert_eq!(model.unreferenced_labels().count(), 0);
180 assert_eq!(model.unresolved_refs().count(), 0);
181 }
182
183 #[test]
184 fn ref_family_recognized() {
185 let model = model_of(
186 "\\pageref{x}\\eqref{x}\\autoref{x}\\nameref{x}\\Cref{x}\\vref{x}\\Vref{x}\\cpageref{x}\n",
187 );
188 let kinds: Vec<_> = model.refs().iter().map(|r| r.command).collect();
189 assert_eq!(
190 kinds,
191 vec![
192 RefCommand::PageRef,
193 RefCommand::EqRef,
194 RefCommand::AutoRef,
195 RefCommand::NameRef,
196 RefCommand::CrefUpper,
197 RefCommand::Vref,
198 RefCommand::VrefUpper,
199 RefCommand::CpageRef,
200 ]
201 );
202 }
203
204 #[test]
205 fn non_ref_commands_ignored() {
206 let model = model_of("\\textbf{x}\\section{Hi}\\emph{y}\n");
207 assert_eq!(model.labels().len(), 0);
208 assert_eq!(model.refs().len(), 0);
209 }
210
211 #[test]
212 fn cref_splits_comma_list() {
213 let model = model_of("\\cref{a,b,c}\n");
214 let names: Vec<_> = model.refs().iter().map(|r| r.name.as_str()).collect();
215 assert_eq!(names, vec!["a", "b", "c"]);
216 assert!(model.refs().iter().all(|r| r.command == RefCommand::Cref));
217 let range = model.refs()[0].range;
219 assert!(model.refs().iter().all(|r| r.range == range));
220 }
221
222 #[test]
223 fn plain_ref_does_not_split() {
224 let model = model_of("\\ref{a,b}\n");
225 assert_eq!(model.refs().len(), 1);
226 assert_eq!(model.refs()[0].name, "a,b");
227 }
228
229 #[test]
230 fn cref_empty_and_blank_keys_dropped() {
231 assert_eq!(model_of("\\cref{}\n").refs().len(), 0);
232 let model = model_of("\\cref{a,,b}\n");
233 let names: Vec<_> = model.refs().iter().map(|r| r.name.as_str()).collect();
234 assert_eq!(names, vec!["a", "b"]);
235 }
236
237 #[test]
238 fn unresolved_ref_when_no_label() {
239 let model = model_of("\\ref{missing}\n");
240 assert!(!model.refs()[0].resolved);
241 assert_eq!(model.unresolved_refs().count(), 1);
242 }
243
244 #[test]
245 fn unreferenced_label_reported() {
246 let model = model_of("\\label{x}\n");
247 assert_eq!(model.unreferenced_labels().count(), 1);
248 }
249
250 #[test]
251 fn duplicate_labels_preserved() {
252 let model = model_of("\\label{x}\\label{x}\\ref{x}\n");
253 assert_eq!(model.labels().len(), 2);
254 assert!(model.labels().iter().all(|l| l.referenced));
255 assert!(model.refs()[0].resolved);
256 }
257
258 #[test]
259 fn nested_macro_key_skipped() {
260 let model = model_of("\\label{\\foo}\n");
261 assert_eq!(model.labels().len(), 0);
262 }
263
264 #[test]
265 fn label_collected_inside_environment() {
266 let model = model_of("\\begin{figure}\n\\label{fig:one}\n\\end{figure}\n");
267 assert_eq!(model.labels().len(), 1);
268 assert_eq!(model.labels()[0].name, "fig:one");
269 }
270}