1pub mod builder;
10pub mod completion;
11pub mod define;
12pub mod doc;
13pub mod expl3;
14pub mod label;
15pub mod math;
16pub mod mode;
17pub mod outline;
18pub mod pkgmeta;
19pub mod signature;
20pub mod tikz;
21pub mod xparse;
22
23pub use define::{DefSite, DefSiteKind, scan_definition_sites, scan_definitions};
24pub use doc::{DocAssociation, DocKind, doc_associations};
25pub use label::{
26 CitationRef, ColorDef, ColorDefKind, GlossaryDef, GlossaryDefKind, LabelDef, LabelId, LabelRef,
27 RefCommand, RefId,
28};
29pub use math::{
30 DelimiterRole, MathAtom, MathAtomInfo, MathAtoms, MathClass, NAMED_MATH_OPERATORS, math_atoms,
31 math_char_info, math_command_info,
32};
33pub use mode::{Mode, ModeIndex, argument_domain};
34pub use outline::{LabelContext, OutlineItem, OutlineSymbol, label_context, outline};
35pub use pkgmeta::{NeedsFormatDecl, OptionDecl, ProvidesDecl, ProvidesKind};
36pub use signature::{
37 ArgKind, ArgSpec, ArgumentDomain, CommandSig, ContentKind, EnvironmentSig, SignatureDb,
38 Signatures, match_arg_slot, match_arg_slot_index, match_verbatim_arg_slot,
39};
40
41use std::collections::BTreeSet;
42
43use smol_str::SmolStr;
44
45use crate::declarations::ResolvedDeclarations;
46use crate::syntax::SyntaxNode;
47
48#[derive(Debug, Default, PartialEq, Eq)]
54pub struct SemanticModel {
55 pub(crate) labels: Vec<LabelDef>,
56 pub(crate) refs: Vec<LabelRef>,
57 pub(crate) citations: Vec<CitationRef>,
58 pub(crate) glossary_defs: Vec<GlossaryDef>,
60 pub(crate) color_defs: Vec<ColorDef>,
63 pub(crate) nocite_all: bool,
67 pub(crate) provides: Option<ProvidesDecl>,
70 pub(crate) needs_format: Option<NeedsFormatDecl>,
72 pub(crate) options: Vec<OptionDecl>,
74 pub(crate) declared_key_commands: BTreeSet<SmolStr>,
80}
81
82impl SemanticModel {
83 pub fn build(root: &SyntaxNode) -> Self {
85 builder::build(root)
86 }
87
88 pub fn build_with_declarations(root: &SyntaxNode, declared: &ResolvedDeclarations) -> Self {
90 builder::build_with_declarations(root, declared)
91 }
92
93 pub fn labels(&self) -> &[LabelDef] {
94 &self.labels
95 }
96
97 pub fn label(&self, id: LabelId) -> &LabelDef {
98 &self.labels[id.0 as usize]
99 }
100
101 pub fn refs(&self) -> &[LabelRef] {
102 &self.refs
103 }
104
105 pub fn citations(&self) -> &[CitationRef] {
107 &self.citations
108 }
109
110 pub fn is_key_argument_command(&self, name: &str) -> bool {
119 builder::key_argument_command(name) || self.declared_key_commands.contains(name)
120 }
121
122 pub fn glossary_defs(&self) -> &[GlossaryDef] {
125 &self.glossary_defs
126 }
127
128 pub fn color_defs(&self) -> &[ColorDef] {
131 &self.color_defs
132 }
133
134 pub fn has_wildcard_nocite(&self) -> bool {
136 self.nocite_all
137 }
138
139 pub fn provides(&self) -> Option<&ProvidesDecl> {
141 self.provides.as_ref()
142 }
143
144 pub fn needs_format(&self) -> Option<&NeedsFormatDecl> {
146 self.needs_format.as_ref()
147 }
148
149 pub fn options(&self) -> &[OptionDecl] {
151 &self.options
152 }
153
154 pub fn reference(&self, id: RefId) -> &LabelRef {
155 &self.refs[id.0 as usize]
156 }
157
158 pub fn unreferenced_labels(&self) -> impl Iterator<Item = LabelId> + '_ {
167 (0..self.labels.len())
168 .map(LabelId::from_index)
169 .filter(move |id| !self.label(*id).referenced)
170 }
171
172 pub fn unresolved_refs(&self) -> impl Iterator<Item = RefId> + '_ {
179 (0..self.refs.len())
180 .map(RefId::from_index)
181 .filter(move |id| !self.reference(*id).resolved)
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188 use crate::declarations::Declarations;
189 use crate::parser::parse;
190
191 fn model_of(src: &str) -> SemanticModel {
192 SemanticModel::build(&SyntaxNode::new_root(parse(src).green))
193 }
194
195 fn declared_model(src: &str, json: &str) -> SemanticModel {
196 let declared = serde_json::from_str::<Declarations>(json)
197 .expect("declarations deserialize")
198 .resolve()
199 .expect("declarations resolve");
200 SemanticModel::build_with_declarations(&SyntaxNode::new_root(parse(src).green), &declared)
201 }
202
203 #[test]
204 fn label_creates_def() {
205 let model = model_of("\\label{sec:intro}\n");
206 assert_eq!(model.labels().len(), 1);
207 assert_eq!(model.labels()[0].name, "sec:intro");
208 assert!(!model.labels()[0].referenced);
209 }
210
211 #[test]
212 fn ref_creates_use() {
213 let model = model_of("\\ref{sec:intro}\n");
214 assert_eq!(model.refs().len(), 1);
215 assert_eq!(model.refs()[0].name, "sec:intro");
216 assert_eq!(model.refs()[0].command, RefCommand::Ref);
217 assert!(!model.refs()[0].resolved);
218 }
219
220 #[test]
221 fn label_and_ref_resolve() {
222 let model = model_of("\\label{a}\\ref{a}\n");
223 assert!(model.labels()[0].referenced);
224 assert!(model.refs()[0].resolved);
225 assert_eq!(model.unreferenced_labels().count(), 0);
226 assert_eq!(model.unresolved_refs().count(), 0);
227 }
228
229 #[test]
230 fn ref_family_recognized() {
231 let model = model_of(
232 "\\pageref{x}\\eqref{x}\\autoref{x}\\nameref{x}\\Cref{x}\\vref{x}\\Vref{x}\\cpageref{x}\n",
233 );
234 let kinds: Vec<_> = model.refs().iter().map(|r| r.command).collect();
235 assert_eq!(
236 kinds,
237 vec![
238 RefCommand::PageRef,
239 RefCommand::EqRef,
240 RefCommand::AutoRef,
241 RefCommand::NameRef,
242 RefCommand::CrefUpper,
243 RefCommand::Vref,
244 RefCommand::VrefUpper,
245 RefCommand::CpageRef,
246 ]
247 );
248 }
249
250 #[test]
251 fn non_ref_commands_ignored() {
252 let model = model_of("\\textbf{x}\\section{Hi}\\emph{y}\n");
253 assert_eq!(model.labels().len(), 0);
254 assert_eq!(model.refs().len(), 0);
255 }
256
257 #[test]
258 fn cref_splits_comma_list() {
259 let model = model_of("\\cref{a,b,c}\n");
260 let names: Vec<_> = model.refs().iter().map(|r| r.name.as_str()).collect();
261 assert_eq!(names, vec!["a", "b", "c"]);
262 assert!(model.refs().iter().all(|r| r.command == RefCommand::Cref));
263 let range = model.refs()[0].range;
264 assert!(model.refs().iter().all(|r| r.range == range));
265 }
266
267 #[test]
268 fn declared_reference_inherits_target_key_cardinality() {
269 let model = declared_model(
270 "\\label{a}\\label{b}\\one{a,b}\\many{a,b}\n",
271 r#"{"commands": {
272 "one": {"like": "eqref"},
273 "many": {"like": "cref"}
274 }}"#,
275 );
276 let names: Vec<_> = model.refs().iter().map(|r| r.name.as_str()).collect();
277 assert_eq!(names, vec!["a,b", "a", "b"]);
278 assert_eq!(model.refs()[0].command, RefCommand::EqRef);
279 assert!(
280 model.refs()[1..]
281 .iter()
282 .all(|reference| reference.command == RefCommand::Cref)
283 );
284 assert!(model.labels().iter().all(|label| label.referenced));
285 }
286
287 #[test]
288 fn declared_environment_inherits_label_key_behavior() {
289 let model = declared_model(
290 "\\begin{mylisting}[label=custom:list]\nbody\n\\end{mylisting}\n",
291 r#"{"environments": {"mylisting": {"like": "lstlisting"}}}"#,
292 );
293 assert_eq!(model.labels().len(), 1);
294 assert_eq!(model.labels()[0].name, "custom:list");
295 }
296
297 #[test]
298 fn declared_citation_and_nocite_aliases_are_collected() {
299 let model = declared_model(
300 "\\sources{one,two}\\everything{*}\n",
301 r#"{"commands": {
302 "sources": {"like": "parencite"},
303 "everything": {"like": "nocite"}
304 }}"#,
305 );
306 let names: Vec<_> = model
307 .citations()
308 .iter()
309 .map(|citation| citation.name.as_str())
310 .collect();
311 assert_eq!(names, vec!["one", "two"]);
312 assert!(
313 model
314 .citations()
315 .iter()
316 .all(|citation| citation.command == "sources")
317 );
318 assert!(model.has_wildcard_nocite());
319 }
320
321 #[test]
322 fn a_declared_alias_is_a_key_argument_command_by_name() {
323 let model = declared_model(
324 "\\myref{\\textbf{a}}\\wrapper{a}\n",
325 r#"{"commands": {
326 "myref": {"like": "cref"},
327 "wrapper": {"like": "citeauthor"}
328 }}"#,
329 );
330 assert!(model.refs().is_empty(), "the nested-macro key is skipped");
331 assert!(model.is_key_argument_command("myref"));
332 assert!(model.is_key_argument_command("wrapper"));
333 assert!(
334 model.is_key_argument_command("cref"),
335 "built-ins still pass"
336 );
337 assert!(!model.is_key_argument_command("emph"));
338 assert!(
339 !SemanticModel::build(&SyntaxNode::new_root(parse("\\myref{a}\n").green))
340 .is_key_argument_command("myref"),
341 "undeclared, the alias is an ordinary command"
342 );
343 }
344
345 #[test]
346 fn plain_ref_does_not_split() {
347 let model = model_of("\\ref{a,b}\n");
348 assert_eq!(model.refs().len(), 1);
349 assert_eq!(model.refs()[0].name, "a,b");
350 }
351
352 #[test]
353 fn cref_empty_and_blank_keys_dropped() {
354 assert_eq!(model_of("\\cref{}\n").refs().len(), 0);
355 let model = model_of("\\cref{a,,b}\n");
356 let names: Vec<_> = model.refs().iter().map(|r| r.name.as_str()).collect();
357 assert_eq!(names, vec!["a", "b"]);
358 }
359
360 #[test]
361 fn unresolved_ref_when_no_label() {
362 let model = model_of("\\ref{missing}\n");
363 assert!(!model.refs()[0].resolved);
364 assert_eq!(model.unresolved_refs().count(), 1);
365 }
366
367 #[test]
368 fn unreferenced_label_reported() {
369 let model = model_of("\\label{x}\n");
370 assert_eq!(model.unreferenced_labels().count(), 1);
371 }
372
373 #[test]
374 fn duplicate_labels_preserved() {
375 let model = model_of("\\label{x}\\label{x}\\ref{x}\n");
376 assert_eq!(model.labels().len(), 2);
377 assert!(model.labels().iter().all(|l| l.referenced));
378 assert!(model.refs()[0].resolved);
379 }
380
381 #[test]
382 fn nested_macro_key_skipped() {
383 let model = model_of("\\label{\\foo}\n");
384 assert_eq!(model.labels().len(), 0);
385 }
386
387 #[test]
388 fn label_collected_inside_environment() {
389 let model = model_of("\\begin{figure}\n\\label{fig:one}\n\\end{figure}\n");
390 assert_eq!(model.labels().len(), 1);
391 assert_eq!(model.labels()[0].name, "fig:one");
392 }
393}