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