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