1use std::collections::{HashMap, HashSet};
29
30use crate::ast::{
31 AstNode, Command, Optional, child, children, command_name, control_word_range,
32 group_command_name, group_inner_source, nth_group, nth_group_inner, nth_group_text,
33};
34use crate::semantic::signature::{
35 ArgKind, ArgSpec, CommandSig, ContentKind, EnvironmentSig, SignatureDb,
36};
37use crate::semantic::xparse;
38use crate::syntax::{SyntaxKind, SyntaxNode};
39use rowan::{NodeOrToken, TextRange, TextSize};
40use smol_str::SmolStr;
41
42pub fn scan_definitions(root: &SyntaxNode) -> SignatureDb {
47 let mut db = SignatureDb::default();
48 let mut bodies: HashMap<SmolStr, DefBody> = HashMap::new();
52 let mut env_bodies: HashMap<SmolStr, DefBody> = HashMap::new();
57
58 for command in root
59 .descendants()
60 .filter(|node| node.kind() == SyntaxKind::COMMAND)
61 {
62 let Some(name) = command_name(&command) else {
63 continue;
64 };
65 match DefKind::of(&name) {
66 Some(DefKind::Command) => scan_newcommand(&command, &mut db, &mut bodies),
67 Some(DefKind::Def) => scan_def(&command, &mut db, &mut bodies),
68 Some(DefKind::Environment) => scan_newenvironment(&command, &mut db, &mut env_bodies),
69 Some(DefKind::XparseCommand) => scan_xparse_command(&command, &mut db, &mut bodies),
70 Some(DefKind::XparseEnvironment) => {
71 scan_xparse_environment(&command, &mut db, &mut env_bodies)
72 }
73 Some(DefKind::VerbatimEnvironment) => {
74 scan_verbatim_environment(&name, &command, &mut db)
75 }
76 None => {}
77 }
78 }
79
80 apply_verbatim_flags(&mut db, &bodies);
81 apply_verbatim_env_flags(&mut db, &env_bodies, &bodies);
82 db
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum DefSiteKind {
89 Command,
90 Environment,
91}
92
93#[derive(Debug, Clone, PartialEq, Eq)]
99pub struct DefSite {
100 pub name: SmolStr,
102 pub kind: DefSiteKind,
103 pub name_range: TextRange,
110 pub range: TextRange,
113}
114
115pub fn scan_definition_sites(root: &SyntaxNode) -> Vec<DefSite> {
121 let mut sites = Vec::new();
122 for command in root
123 .descendants()
124 .filter(|node| node.kind() == SyntaxKind::COMMAND)
125 {
126 let Some(name) = command_name(&command) else {
127 continue;
128 };
129 let site = match DefKind::of(&name) {
130 Some(DefKind::Command | DefKind::XparseCommand) => command_def_site(&command),
131 Some(DefKind::Def) => def_def_site(&command),
132 Some(
133 DefKind::Environment | DefKind::XparseEnvironment | DefKind::VerbatimEnvironment,
134 ) => environment_def_site(&command),
135 None => None,
136 };
137 sites.extend(site);
138 }
139 sites
140}
141
142fn command_def_site(command: &SyntaxNode) -> Option<DefSite> {
147 let def = resolve_command_def(command)?;
148 let name_range = if def.first_arg_group == 1 {
149 let group = nth_group(command, 0)?;
150 child::<Command>(&group)?.control_word_range()?
151 } else {
152 control_word_range(&def.host)?
153 };
154 Some(DefSite {
155 name: SmolStr::new(&def.name),
156 kind: DefSiteKind::Command,
157 name_range,
158 range: TextRange::new(
159 command.text_range().start(),
160 command.text_range().end().max(def.host.text_range().end()),
161 ),
162 })
163}
164
165fn def_def_site(command: &SyntaxNode) -> Option<DefSite> {
168 let name_node = adjacent_sibling_command(command)?;
169 let name = command_name(&name_node)?;
170 let name_range = control_word_range(&name_node)?;
171 Some(DefSite {
172 name: SmolStr::new(&name),
173 kind: DefSiteKind::Command,
174 name_range,
175 range: TextRange::new(command.text_range().start(), name_node.text_range().end()),
176 })
177}
178
179fn environment_def_site(command: &SyntaxNode) -> Option<DefSite> {
183 let (inner_range, text) = nth_group_inner(command, 0)?;
184 let trimmed = text.trim();
185 if trimmed.is_empty() {
186 return None;
187 }
188 let leading = text.len() - text.trim_start().len();
189 let name_range = TextRange::at(
190 inner_range.start() + TextSize::new(leading as u32),
191 TextSize::new(trimmed.len() as u32),
192 );
193 Some(DefSite {
194 name: SmolStr::new(trimmed),
195 kind: DefSiteKind::Environment,
196 name_range,
197 range: command.text_range(),
198 })
199}
200
201struct DefBody {
205 signal: bool,
208 called: Vec<SmolStr>,
211}
212
213fn apply_verbatim_flags(db: &mut SignatureDb, bodies: &HashMap<SmolStr, DefBody>) {
225 let verbatim: Vec<SmolStr> = bodies
226 .keys()
227 .filter(|name| {
228 db.command(name).is_some_and(|sig| !sig.args.is_empty())
230 && reaches_signal(name, bodies, &mut HashSet::new())
231 })
232 .cloned()
233 .collect();
234
235 for name in verbatim {
236 if let Some(mut sig) = db.command(&name).cloned() {
237 sig.args.to_mut().pop(); sig.verbatim = true;
239 db.insert_command(name, sig);
240 }
241 }
242}
243
244fn apply_verbatim_env_flags(
253 db: &mut SignatureDb,
254 env_bodies: &HashMap<SmolStr, DefBody>,
255 bodies: &HashMap<SmolStr, DefBody>,
256) {
257 let verbatim: Vec<SmolStr> = env_bodies
258 .iter()
259 .filter(|(name, body)| {
260 db.environment(name).is_some() && reaches_signal_body(body, bodies, &mut HashSet::new())
261 })
262 .map(|(name, _)| name.clone())
263 .collect();
264
265 for name in verbatim {
266 if let Some(mut sig) = db.environment(&name).cloned() {
267 sig.verbatim_body = true;
268 sig.reflow = false; db.insert_environment(name, sig);
270 }
271 }
272}
273
274fn reaches_signal(
280 name: &str,
281 bodies: &HashMap<SmolStr, DefBody>,
282 visited: &mut HashSet<SmolStr>,
283) -> bool {
284 if !visited.insert(SmolStr::new(name)) {
285 return false;
286 }
287 let Some(body) = bodies.get(name) else {
288 return false;
289 };
290 reaches_signal_body(body, bodies, visited)
291}
292
293fn reaches_signal_body(
299 body: &DefBody,
300 bodies: &HashMap<SmolStr, DefBody>,
301 visited: &mut HashSet<SmolStr>,
302) -> bool {
303 body.signal
304 || body
305 .called
306 .iter()
307 .any(|callee| reaches_signal(callee, bodies, visited))
308}
309
310fn catcode_signal(body: &str) -> bool {
315 body.contains("\\@makeother")
316 || body.contains("\\@sanitize")
317 || body.contains("\\dospecials")
318 || (body.contains("\\catcode") && body.contains("12"))
321}
322
323fn called_macros(body: &str) -> Vec<SmolStr> {
328 body.match_indices('\\')
329 .filter_map(|(pos, _)| {
330 let after = &body[pos + 1..];
331 let len: usize = after
332 .chars()
333 .take_while(|c| c.is_ascii_alphabetic() || *c == '@')
334 .map(char::len_utf8)
335 .sum();
336 (len > 0).then(|| SmolStr::new(&after[..len]))
337 })
338 .collect()
339}
340
341const DELEGATING_PRIMITIVES: &[&str] = &["secdef", "@startsection", "@dblarg", "@sect", "@ssect"];
351
352fn keeps_builtin_over_arity0(name: &str, arity: usize, body: &DefBody) -> bool {
363 arity == 0
364 && body
365 .called
366 .iter()
367 .any(|callee| DELEGATING_PRIMITIVES.contains(&callee.as_str()))
368 && crate::semantic::signature::builtin()
369 .command(name)
370 .is_some()
371}
372
373pub fn is_definition_command(name: &str) -> bool {
380 DefKind::of(name).is_some()
381}
382
383enum DefKind {
385 Command,
386 Def,
387 Environment,
388 XparseCommand,
389 XparseEnvironment,
390 VerbatimEnvironment,
395}
396
397impl DefKind {
398 fn of(name: &str) -> Option<Self> {
399 Some(match name {
400 "newcommand" | "renewcommand" | "providecommand" | "DeclareRobustCommand" => {
401 DefKind::Command
402 }
403 "def" | "edef" | "gdef" | "xdef" => DefKind::Def,
406 "newenvironment" | "renewenvironment" => DefKind::Environment,
407 "NewDocumentCommand"
408 | "RenewDocumentCommand"
409 | "ProvideDocumentCommand"
410 | "DeclareDocumentCommand" => DefKind::XparseCommand,
411 "NewDocumentEnvironment"
412 | "RenewDocumentEnvironment"
413 | "ProvideDocumentEnvironment"
414 | "DeclareDocumentEnvironment" => DefKind::XparseEnvironment,
415 "lstnewenvironment" | "DefineVerbatimEnvironment" => DefKind::VerbatimEnvironment,
418 _ => return None,
419 })
420 }
421}
422
423fn scan_newcommand(
429 command: &SyntaxNode,
430 db: &mut SignatureDb,
431 bodies: &mut HashMap<SmolStr, DefBody>,
432) {
433 let Some(def) = resolve_command_def(command) else {
434 return;
435 };
436 let (arity, first_optional) = newcommand_arity(&def.host);
437 record_body(
440 bodies,
441 &def.name,
442 nth_group(&def.host, def.first_arg_group).as_ref(),
443 );
444 if bodies
448 .get(def.name.as_str())
449 .is_some_and(|body| keeps_builtin_over_arity0(&def.name, arity, body))
450 {
451 return;
452 }
453 db.insert_command(
454 def.name,
455 CommandSig {
456 args: latex2e_args(arity, first_optional).into(),
457 sectioning: None,
458 verbatim: false,
459 verbatim_delimited: false,
460 rule: false,
461 inline: false,
462 },
463 );
464}
465
466fn scan_def(command: &SyntaxNode, db: &mut SignatureDb, bodies: &mut HashMap<SmolStr, DefBody>) {
474 let Some(name_node) = adjacent_sibling_command(command) else {
475 return;
476 };
477 let Some(name) = command_name(&name_node) else {
478 return;
479 };
480 let (arity, body) = def_params_and_body(&name_node);
481 record_body(bodies, &name, body.as_ref());
482 if bodies
485 .get(name.as_str())
486 .is_some_and(|body| keeps_builtin_over_arity0(&name, arity, body))
487 {
488 return;
489 }
490 db.insert_command(
491 name,
492 CommandSig {
493 args: latex2e_args(arity, false).into(),
497 sectioning: None,
498 verbatim: false,
499 verbatim_delimited: false,
500 rule: false,
501 inline: false,
502 },
503 );
504}
505
506fn def_params_and_body(name_node: &SyntaxNode) -> (usize, Option<SyntaxNode>) {
519 if let Some(body) = nth_group(name_node, 0) {
521 return (0, Some(body));
522 }
523 let mut arity = 0usize;
525 let mut next = name_node.next_sibling_or_token();
526 while let Some(element) = next {
527 match element {
528 NodeOrToken::Token(token) if is_trivia(token.kind()) => {
529 next = token.next_sibling_or_token();
530 }
531 NodeOrToken::Token(token) if token.kind() == SyntaxKind::HASH => {
532 arity += 1;
533 next = token.next_sibling_or_token();
534 }
535 NodeOrToken::Token(token) if token.kind() == SyntaxKind::WORD => {
537 next = token.next_sibling_or_token();
538 }
539 NodeOrToken::Node(node) if node.kind() == SyntaxKind::GROUP => {
540 return (arity.min(9), Some(node));
541 }
542 _ => return (arity.min(9), None),
543 }
544 }
545 (arity.min(9), None)
546}
547
548fn record_body(bodies: &mut HashMap<SmolStr, DefBody>, name: &str, body: Option<&SyntaxNode>) {
551 let text = body.map(group_inner_source).unwrap_or_default();
552 bodies.insert(
553 SmolStr::new(name),
554 DefBody {
555 signal: catcode_signal(&text),
556 called: called_macros(&text),
557 },
558 );
559}
560
561fn scan_newenvironment(
566 command: &SyntaxNode,
567 db: &mut SignatureDb,
568 env_bodies: &mut HashMap<SmolStr, DefBody>,
569) {
570 let Some(name) = nth_group_text(command, 0) else {
571 return;
572 };
573 let name = name.trim();
574 if name.is_empty() {
575 return;
576 }
577 record_body(env_bodies, name, nth_group(command, 1).as_ref());
578 let (arity, first_optional) = newcommand_arity(command);
579 db.insert_environment(name, environment_sig(latex2e_args(arity, first_optional)));
580}
581
582fn scan_verbatim_environment(defining_command: &str, command: &SyntaxNode, db: &mut SignatureDb) {
592 let Some(name) = nth_group_text(command, 0) else {
593 return;
594 };
595 let name = name.trim();
596 if name.is_empty() {
597 return;
598 }
599 let args = if defining_command == "lstnewenvironment" {
600 let (arity, first_optional) = newcommand_arity(command);
601 latex2e_args(arity, first_optional)
602 } else {
603 latex2e_args(1, true)
605 };
606 let mut sig = environment_sig(args);
607 sig.verbatim_body = true;
608 sig.reflow = false;
609 db.insert_environment(name, sig);
610}
611
612fn scan_xparse_command(
617 command: &SyntaxNode,
618 db: &mut SignatureDb,
619 bodies: &mut HashMap<SmolStr, DefBody>,
620) {
621 let Some(def) = resolve_command_def(command) else {
622 return;
623 };
624 let Some(spec) = nth_group(&def.host, def.first_arg_group) else {
625 return;
626 };
627 record_body(
629 bodies,
630 &def.name,
631 nth_group(&def.host, def.first_arg_group + 1).as_ref(),
632 );
633 db.insert_command(
634 def.name,
635 CommandSig {
636 args: xparse::parse_spec(&group_inner_source(&spec)).into(),
637 sectioning: None,
638 verbatim: false,
639 verbatim_delimited: false,
640 rule: false,
641 inline: false,
642 },
643 );
644}
645
646struct CommandDef {
657 name: String,
658 host: SyntaxNode,
659 first_arg_group: usize,
660}
661
662fn resolve_command_def(command: &SyntaxNode) -> Option<CommandDef> {
666 if command.children().any(|c| c.kind() == SyntaxKind::GROUP) {
669 let name = nth_group(command, 0)
670 .as_ref()
671 .and_then(group_command_name)?;
672 return Some(CommandDef {
673 name,
674 host: command.clone(),
675 first_arg_group: 1,
676 });
677 }
678 let sibling = adjacent_sibling_command(command)?;
682 let name = command_name(&sibling)?;
683 Some(CommandDef {
684 name,
685 host: sibling,
686 first_arg_group: 0,
687 })
688}
689
690fn adjacent_sibling_command(command: &SyntaxNode) -> Option<SyntaxNode> {
696 let mut next = command.next_sibling_or_token();
697 while let Some(element) = next {
698 match element {
699 NodeOrToken::Token(token) if is_trivia(token.kind()) => {
700 next = token.next_sibling_or_token();
701 }
702 NodeOrToken::Node(node) if node.kind() == SyntaxKind::COMMAND => return Some(node),
703 _ => return None,
704 }
705 }
706 None
707}
708
709fn is_trivia(kind: SyntaxKind) -> bool {
712 matches!(
713 kind,
714 SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE | SyntaxKind::COMMENT
715 )
716}
717
718fn scan_xparse_environment(
722 command: &SyntaxNode,
723 db: &mut SignatureDb,
724 env_bodies: &mut HashMap<SmolStr, DefBody>,
725) {
726 let Some(name) = nth_group_text(command, 0) else {
727 return;
728 };
729 let name = name.trim();
730 if name.is_empty() {
731 return;
732 }
733 let Some(spec) = nth_group(command, 1) else {
734 return;
735 };
736 record_body(env_bodies, name, nth_group(command, 2).as_ref());
737 db.insert_environment(
738 name,
739 environment_sig(xparse::parse_spec(&group_inner_source(&spec))),
740 );
741}
742
743fn newcommand_arity(command: &SyntaxNode) -> (usize, bool) {
747 let optionals: Vec<Optional> = children::<Optional>(command).collect();
748 let arity = optionals
749 .first()
750 .map(|o| o.syntax())
751 .and_then(optional_number)
752 .unwrap_or(0)
753 .min(9); (arity, optionals.len() >= 2)
755}
756
757fn optional_number(node: &SyntaxNode) -> Option<usize> {
760 let text = node.text().to_string();
761 let inner = text.strip_prefix('[').unwrap_or(&text);
762 let inner = inner.strip_suffix(']').unwrap_or(inner);
763 inner.trim().parse().ok()
764}
765
766fn latex2e_args(arity: usize, first_optional: bool) -> Vec<ArgSpec> {
769 (0..arity)
770 .map(|i| {
771 if i == 0 && first_optional {
772 ArgSpec {
773 required: false,
774 kind: ArgKind::Bracket,
775 content: ContentKind::Opaque,
776 }
777 } else {
778 ArgSpec {
779 required: true,
780 kind: ArgKind::Brace,
781 content: ContentKind::Opaque,
782 }
783 }
784 })
785 .collect()
786}
787
788fn environment_sig(args: Vec<ArgSpec>) -> EnvironmentSig {
792 EnvironmentSig {
793 args: args.into(),
794 verbatim_body: false,
795 verbatim_arg: false,
798 math: false,
799 code: false,
800 align: false,
801 reflow: true,
802 no_indent: false,
803 list: false,
806 block: false,
810 outline: None,
813 }
814}
815
816#[cfg(test)]
817mod tests {
818 use super::*;
819 use crate::parser::{parse, reconstruct};
820
821 fn db_of(src: &str) -> SignatureDb {
822 assert_eq!(reconstruct(src), src, "reconstruct must round-trip");
824 scan_definitions(&SyntaxNode::new_root(parse(src).green))
825 }
826
827 fn arg_kinds(args: &[ArgSpec]) -> Vec<ArgKind> {
828 args.iter().map(|a| a.kind).collect()
829 }
830
831 #[test]
832 fn newcommand_counts_mandatory_args() {
833 let db = db_of("\\newcommand{\\foo}[2]{#1#2}\n");
834 let sig = db.command("foo").expect("foo defined");
835 assert_eq!(arg_kinds(&sig.args), vec![ArgKind::Brace, ArgKind::Brace]);
836 assert!(sig.args.iter().all(|a| a.required));
837 }
838
839 #[test]
840 fn newcommand_optional_first_arg() {
841 let db = db_of("\\newcommand{\\foo}[2][d]{#1#2}\n");
842 let sig = db.command("foo").expect("foo defined");
843 assert_eq!(arg_kinds(&sig.args), vec![ArgKind::Bracket, ArgKind::Brace]);
844 assert!(!sig.args[0].required);
845 assert!(sig.args[1].required);
846 }
847
848 #[test]
849 fn newcommand_zero_args() {
850 let db = db_of("\\newcommand{\\foo}{bar}\n");
851 assert!(db.command("foo").expect("foo defined").args.is_empty());
852 }
853
854 #[test]
855 fn renew_and_provide_recognized() {
856 let db = db_of("\\renewcommand{\\a}[1]{x}\\providecommand{\\b}[1]{y}\n");
857 assert_eq!(db.command("a").unwrap().args.len(), 1);
858 assert_eq!(db.command("b").unwrap().args.len(), 1);
859 }
860
861 #[test]
862 fn secdef_redefinition_keeps_builtin_prose() {
863 let db = db_of("\\renewcommand{\\section}{\\secdef \\a \\b}\n");
868 assert!(
869 db.command("section").is_none(),
870 "the delegating redefinition must not be recorded as a scanned override"
871 );
872 let sigs = crate::semantic::signature::Signatures::new(&db);
873 let sig = sigs.command("section").expect("built-in section survives");
874 let last = sig.args.last().expect("section keeps its title argument");
875 assert_eq!(
876 last.content,
877 crate::semantic::signature::ContentKind::Prose,
878 "the title argument stays prose (reflowable)"
879 );
880 }
881
882 #[test]
883 fn genuine_zero_arg_redefinition_downgrades_builtin() {
884 let db = db_of("\\renewcommand{\\section}{\\textbf{Fixed}}\n");
888 let sig = db
889 .command("section")
890 .expect("genuine 0-arg redefinition is recorded");
891 assert!(
892 sig.args.is_empty(),
893 "no delegation means the scanned 0-arg signature wins"
894 );
895 }
896
897 #[test]
898 fn secdef_redefinition_of_unknown_still_records() {
899 let db = db_of("\\renewcommand{\\mysec}{\\secdef \\a \\b}\n");
903 let sig = db.command("mysec").expect("unknown name is still recorded");
904 assert!(sig.args.is_empty());
905 }
906
907 #[test]
908 fn newenvironment_args() {
909 let db = db_of("\\newenvironment{thm}[1]{begin #1}{end}\n");
910 let sig = db.environment("thm").expect("thm defined");
911 assert_eq!(arg_kinds(&sig.args), vec![ArgKind::Brace]);
912 assert!(sig.reflow);
913 assert!(!sig.verbatim_body);
914 assert!(!sig.math);
915 }
916
917 #[test]
918 fn xparse_command_spec() {
919 let db = db_of("\\NewDocumentCommand{\\foo}{m O{d} m}{x}\n");
920 let sig = db.command("foo").expect("foo defined");
921 assert_eq!(
922 arg_kinds(&sig.args),
923 vec![ArgKind::Brace, ArgKind::Bracket, ArgKind::Brace]
924 );
925 }
926
927 #[test]
928 fn xparse_environment_spec() {
929 let db = db_of("\\NewDocumentEnvironment{env}{O{x} m}{a}{b}\n");
930 let sig = db.environment("env").expect("env defined");
931 assert_eq!(arg_kinds(&sig.args), vec![ArgKind::Bracket, ArgKind::Brace]);
932 }
933
934 #[test]
935 fn unbraced_newcommand_extracted() {
936 let db = db_of("\\newcommand\\foo[2]{#1#2}\n");
939 let sig = db.command("foo").expect("foo defined");
940 assert_eq!(arg_kinds(&sig.args), vec![ArgKind::Brace, ArgKind::Brace]);
941 assert!(sig.args.iter().all(|a| a.required));
942 }
943
944 #[test]
945 fn unbraced_optional_first_arg() {
946 let db = db_of("\\newcommand\\foo[2][d]{#1#2}\n");
947 let sig = db.command("foo").expect("foo defined");
948 assert_eq!(arg_kinds(&sig.args), vec![ArgKind::Bracket, ArgKind::Brace]);
949 assert!(!sig.args[0].required);
950 assert!(sig.args[1].required);
951 }
952
953 #[test]
954 fn unbraced_zero_args() {
955 let db = db_of("\\newcommand\\foo{x}\n");
956 assert!(db.command("foo").expect("foo defined").args.is_empty());
957 }
958
959 #[test]
960 fn unbraced_spaced_binds() {
961 let db = db_of("\\newcommand \\foo[1]{x}\n");
963 assert_eq!(db.command("foo").unwrap().args.len(), 1);
964 }
965
966 #[test]
967 fn unbraced_renewcommand() {
968 let db = db_of("\\renewcommand\\foo[1]{x}\n");
969 assert_eq!(db.command("foo").unwrap().args.len(), 1);
970 }
971
972 #[test]
973 fn unbraced_xparse_command() {
974 let db = db_of("\\NewDocumentCommand\\foo{m O{d} m}{x}\n");
975 let sig = db.command("foo").expect("foo defined");
976 assert_eq!(
977 arg_kinds(&sig.args),
978 vec![ArgKind::Brace, ArgKind::Bracket, ArgKind::Brace]
979 );
980 }
981
982 #[test]
983 fn unbraced_stray_text_not_bound() {
984 let db = db_of("\\newcommand foo \\bar{x}\n");
987 assert!(db.command("foo").is_none());
988 assert!(db.command("bar").is_none());
989 }
990
991 #[test]
992 fn redefinition_last_wins() {
993 let db = db_of("\\newcommand{\\foo}[1]{x}\\renewcommand{\\foo}[3]{y}\n");
994 assert_eq!(db.command("foo").unwrap().args.len(), 3);
995 }
996
997 #[test]
998 fn garbage_definition_degrades_to_no_insert() {
999 let db = db_of("\\newcommand\n");
1001 assert!(db.command("foo").is_none());
1002 }
1003
1004 #[test]
1005 fn nested_definition_collected() {
1006 let db = db_of("\\begin{document}\n\\newcommand{\\foo}[1]{x}\n\\end{document}\n");
1007 assert_eq!(db.command("foo").unwrap().args.len(), 1);
1008 }
1009
1010 #[test]
1011 fn commented_definition_ignored() {
1012 let db = db_of("% \\newcommand{\\foo}[1]{x}\n");
1013 assert!(db.command("foo").is_none());
1014 }
1015
1016 #[test]
1017 fn verbatim_makeother_flagged() {
1018 let db = db_of("\\newcommand\\shellcmd[1]{\\@makeother\\$#1}\n");
1021 let sig = db.command("shellcmd").expect("shellcmd defined");
1022 assert!(sig.verbatim);
1023 assert!(sig.args.is_empty());
1024 }
1025
1026 #[test]
1027 fn verbatim_catcode_flagged() {
1028 let db = db_of("\\newcommand\\shellcmd[1]{\\catcode 36=12 #1}\n");
1030 assert!(db.command("shellcmd").expect("shellcmd defined").verbatim);
1031 }
1032
1033 #[test]
1034 fn verbatim_dospecials_flagged() {
1035 let db = db_of("\\newcommand\\shellcmd[1]{\\let\\do\\@makeother\\dospecials #1}\n");
1037 assert!(db.command("shellcmd").expect("shellcmd defined").verbatim);
1038 }
1039
1040 #[test]
1041 fn verbatim_keeps_leading_args() {
1042 let db = db_of("\\newcommand\\mycode[2]{\\@makeother\\$#1#2}\n");
1045 let sig = db.command("mycode").expect("mycode defined");
1046 assert!(sig.verbatim);
1047 assert_eq!(arg_kinds(&sig.args), vec![ArgKind::Brace]);
1048 }
1049
1050 #[test]
1051 fn verbatim_via_chained_helper() {
1052 let db =
1055 db_of("\\newcommand\\setup{\\@makeother\\$}\\newcommand\\shellcmd[1]{\\setup#1}\n");
1056 assert!(db.command("shellcmd").expect("shellcmd defined").verbatim);
1057 assert!(!db.command("setup").expect("setup defined").verbatim);
1059 }
1060
1061 #[test]
1062 fn verbatim_chain_cycle_terminates() {
1063 let db = db_of("\\newcommand\\a[1]{\\b#1}\\newcommand\\b[1]{\\a#1}\n");
1066 assert!(!db.command("a").expect("a defined").verbatim);
1067 assert!(!db.command("b").expect("b defined").verbatim);
1068 }
1069
1070 #[test]
1071 fn ordinary_command_not_verbatim() {
1072 let db = db_of("\\newcommand\\foo[1]{\\emph{#1}}\n");
1073 assert!(!db.command("foo").expect("foo defined").verbatim);
1074 }
1075
1076 #[test]
1077 fn verbatim_needs_an_argument() {
1078 let db = db_of("\\newcommand\\setup{\\@makeother\\$}\n");
1081 assert!(!db.command("setup").expect("setup defined").verbatim);
1082 }
1083
1084 #[test]
1085 fn def_helper_chain_followed() {
1086 let db = db_of("\\def\\setup{\\@makeother\\$}\\newcommand\\shellcmd[1]{\\setup#1}\n");
1089 assert!(db.command("shellcmd").expect("shellcmd defined").verbatim);
1090 assert!(!db.command("setup").expect("setup defined").verbatim);
1092 }
1093
1094 #[test]
1095 fn def_direct_verbatim_flagged() {
1096 let db = db_of("\\def\\shellcmd#1{\\@makeother\\$#1}\n");
1099 let sig = db.command("shellcmd").expect("shellcmd defined");
1100 assert!(sig.verbatim);
1101 assert!(sig.args.is_empty());
1102 }
1103
1104 #[test]
1105 fn def_zero_params() {
1106 let db = db_of("\\def\\foo{x}\n");
1108 let sig = db.command("foo").expect("foo defined");
1109 assert!(sig.args.is_empty());
1110 assert!(!sig.verbatim);
1111 }
1112
1113 #[test]
1114 fn def_counts_params() {
1115 let db = db_of("\\def\\foo#1#2{#1#2}\n");
1117 let sig = db.command("foo").expect("foo defined");
1118 assert_eq!(arg_kinds(&sig.args), vec![ArgKind::Brace, ArgKind::Brace]);
1119 }
1120
1121 #[test]
1122 fn def_variants_scanned() {
1123 let db = db_of("\\edef\\a#1{x}\\gdef\\b{y}\\xdef\\c#1{\\@makeother\\$#1}\n");
1125 assert_eq!(db.command("a").expect("a defined").args.len(), 1);
1126 assert!(db.command("b").expect("b defined").args.is_empty());
1127 let c = db.command("c").expect("c defined");
1128 assert!(c.verbatim);
1129 assert!(c.args.is_empty());
1130 }
1131
1132 #[test]
1133 fn def_chain_through_def_helpers() {
1134 let db = db_of(
1136 "\\def\\inner{\\@makeother\\$}\\def\\outer{\\inner}\\newcommand\\cmd[1]{\\outer#1}\n",
1137 );
1138 assert!(db.command("cmd").expect("cmd defined").verbatim);
1139 }
1140
1141 #[test]
1142 fn verbatim_xparse_flagged() {
1143 let db = db_of("\\NewDocumentCommand\\shellcmd{m}{\\@makeother\\$#1}\n");
1144 let sig = db.command("shellcmd").expect("shellcmd defined");
1145 assert!(sig.verbatim);
1146 assert!(sig.args.is_empty());
1147 }
1148
1149 #[test]
1150 fn env_makeother_flagged() {
1151 let db = db_of("\\newenvironment{shellenv}{\\@makeother\\$}{}\n");
1154 let sig = db.environment("shellenv").expect("shellenv defined");
1155 assert!(sig.verbatim_body);
1156 assert!(!sig.reflow); }
1158
1159 #[test]
1160 fn env_catcode_flagged() {
1161 let db = db_of("\\newenvironment{shellenv}[1]{\\catcode 36=12 }{}\n");
1163 let sig = db.environment("shellenv").expect("shellenv defined");
1164 assert!(sig.verbatim_body);
1165 assert_eq!(arg_kinds(&sig.args), vec![ArgKind::Brace]);
1167 }
1168
1169 #[test]
1170 fn env_via_chained_helper() {
1171 let db =
1174 db_of("\\newcommand\\setup{\\@makeother\\$}\\newenvironment{shellenv}{\\setup}{}\n");
1175 assert!(
1176 db.environment("shellenv")
1177 .expect("shellenv defined")
1178 .verbatim_body
1179 );
1180 }
1181
1182 #[test]
1183 fn env_without_signal_not_flagged() {
1184 let db = db_of("\\newenvironment{remark}{\\par\\noindent\\textbf{Remark.}}{\\par}\n");
1186 let sig = db.environment("remark").expect("remark defined");
1187 assert!(!sig.verbatim_body);
1188 assert!(sig.reflow);
1189 }
1190
1191 #[test]
1192 fn lstnewenvironment_flagged_verbatim() {
1193 let db = db_of("\\lstnewenvironment{demo}[1][code]{\\lstset{#1}}{}\n");
1197 let sig = db.environment("demo").expect("demo defined");
1198 assert!(sig.verbatim_body);
1199 assert!(!sig.reflow);
1200 assert_eq!(arg_kinds(&sig.args), vec![ArgKind::Bracket]);
1201 }
1202
1203 #[test]
1204 fn lstnewenvironment_no_args_flagged_verbatim() {
1205 let db = db_of("\\lstnewenvironment{demo}{}{}\n");
1206 let sig = db.environment("demo").expect("demo defined");
1207 assert!(sig.verbatim_body);
1208 assert!(sig.args.is_empty());
1209 }
1210
1211 #[test]
1212 fn defineverbatimenvironment_flagged_verbatim() {
1213 let db = db_of("\\DefineVerbatimEnvironment{code}{Verbatim}{fontsize=\\small}\n");
1215 let sig = db.environment("code").expect("code defined");
1216 assert!(sig.verbatim_body);
1217 assert!(!sig.reflow);
1218 assert_eq!(arg_kinds(&sig.args), vec![ArgKind::Bracket]);
1219 }
1220
1221 #[test]
1222 fn xparse_env_makeother_flagged() {
1223 let db = db_of("\\NewDocumentEnvironment{shellenv}{O{x}}{\\dospecials}{}\n");
1225 let sig = db.environment("shellenv").expect("shellenv defined");
1226 assert!(sig.verbatim_body);
1227 assert_eq!(arg_kinds(&sig.args), vec![ArgKind::Bracket]);
1228 }
1229
1230 fn sites_of(src: &str) -> Vec<DefSite> {
1231 assert_eq!(reconstruct(src), src, "reconstruct must round-trip");
1232 scan_definition_sites(&SyntaxNode::new_root(parse(src).green))
1233 }
1234
1235 #[test]
1236 fn def_site_newcommand_braced_name_span() {
1237 let src = "\\newcommand{\\foo}[1]{#1}\n";
1238 let sites = sites_of(src);
1239 assert_eq!(sites.len(), 1);
1240 let site = &sites[0];
1241 assert_eq!(site.name, "foo");
1242 assert_eq!(site.kind, DefSiteKind::Command);
1243 assert_eq!(&src[site.name_range], "\\foo");
1244 assert_eq!(&src[site.range], "\\newcommand{\\foo}[1]{#1}");
1245 }
1246
1247 #[test]
1248 fn def_site_newcommand_unbraced_name_span() {
1249 let src = "\\newcommand\\foo[1]{#1}\n";
1250 let sites = sites_of(src);
1251 assert_eq!(sites.len(), 1);
1252 assert_eq!(sites[0].name, "foo");
1253 assert_eq!(&src[sites[0].name_range], "\\foo");
1254 assert_eq!(&src[sites[0].range], "\\newcommand\\foo[1]{#1}");
1255 }
1256
1257 #[test]
1258 fn def_site_def_sibling_name_span() {
1259 let src = "\\def\\foo#1{#1}\n";
1260 let sites = sites_of(src);
1261 assert_eq!(sites.len(), 1);
1262 assert_eq!(sites[0].name, "foo");
1263 assert_eq!(sites[0].kind, DefSiteKind::Command);
1264 assert_eq!(&src[sites[0].name_range], "\\foo");
1265 }
1266
1267 #[test]
1268 fn def_site_xparse_command_name_span() {
1269 let src = "\\NewDocumentCommand{\\foo}{m O{d}}{x}\n";
1270 let sites = sites_of(src);
1271 assert_eq!(sites.len(), 1);
1272 assert_eq!(sites[0].name, "foo");
1273 assert_eq!(&src[sites[0].name_range], "\\foo");
1274 }
1275
1276 #[test]
1277 fn def_site_newenvironment_name_span() {
1278 let src = "\\newenvironment{myenv}{begin}{end}\n";
1279 let sites = sites_of(src);
1280 assert_eq!(sites.len(), 1);
1281 let site = &sites[0];
1282 assert_eq!(site.name, "myenv");
1283 assert_eq!(site.kind, DefSiteKind::Environment);
1284 assert_eq!(&src[site.name_range], "myenv");
1285 }
1286
1287 #[test]
1288 fn def_site_xparse_environment_name_span() {
1289 let src = "\\NewDocumentEnvironment{myenv}{m}{a}{b}\n";
1290 let sites = sites_of(src);
1291 assert_eq!(sites.len(), 1);
1292 assert_eq!(sites[0].name, "myenv");
1293 assert_eq!(sites[0].kind, DefSiteKind::Environment);
1294 assert_eq!(&src[sites[0].name_range], "myenv");
1295 }
1296
1297 #[test]
1298 fn def_site_keeps_every_redefinition() {
1299 let src = "\\newcommand{\\foo}{a}\n\\renewcommand{\\foo}{b}\n";
1301 let sites = sites_of(src);
1302 assert_eq!(sites.len(), 2);
1303 assert!(sites.iter().all(|s| s.name == "foo"));
1304 assert!(sites[0].name_range.start() < sites[1].name_range.start());
1305 }
1306
1307 #[test]
1308 fn def_site_none_for_malformed() {
1309 assert!(sites_of("\\newcommand\n").is_empty());
1310 assert!(sites_of("\\newenvironment{}{a}{b}\n").is_empty());
1311 }
1312}