1use std::{borrow::Cow, env, fmt, fs, path::PathBuf};
2
3use proc_macro2::*;
4use quote::{ToTokens, quote_spanned};
5use syn::{
6 self, Attribute, LitStr, Token,
7 parse::{Parse, ParseStream, discouraged::Speculative},
8 parse_quote, parse_quote_spanned,
9 punctuated::Punctuated,
10 spanned::Spanned,
11};
12
13use once_cell::sync::OnceCell;
14
15macro_rules! ident_spanned {
17 ($span:expr=> $($format_name:tt)+) => {
18 proc_macro2::Ident::new(&format!($($format_name)+), $span)
19 };
20}
21
22macro_rules! ident {
24 ($($tt:tt)*) => {
25 ident_spanned!(proc_macro2::Span::call_site()=> $($tt)*)
26 };
27}
28
29pub fn parse_braces<'a>(input: &syn::parse::ParseBuffer<'a>) -> syn::Result<(syn::token::Brace, syn::parse::ParseBuffer<'a>)> {
30 let r;
31 let b = syn::braced!(r in input);
32 Ok((b, r))
33}
34
35#[expect(unexpected_cfgs)] pub fn is_rust_analyzer() -> bool {
38 cfg!(rust_analyzer)
39}
40
41pub fn crate_core() -> TokenStream {
43 let (ident, module) = if is_rust_analyzer() {
44 let (ident, module) = crate_core_parts();
47 (Cow::Owned(ident), module)
48 } else {
49 static CRATE: OnceCell<(String, &'static str)> = OnceCell::new();
50
51 let (ident, module) = CRATE.get_or_init(crate_core_parts);
52 (Cow::Borrowed(ident.as_str()), *module)
53 };
54
55 let ident = Ident::new(&ident, Span::call_site());
56 if !module.is_empty() {
57 let module = Ident::new(module, Span::call_site());
58 quote! { #ident::#module }
59 } else {
60 ident.to_token_stream()
61 }
62}
63fn crate_core_parts() -> (String, &'static str) {
64 if let Ok(ident) = crate_name("zng") {
65 match ident {
67 FoundCrate::Name(name) => (name, "__proc_macro_util"),
68 FoundCrate::Itself => ("zng".to_owned(), "__proc_macro_util"),
69 }
70 } else if let Ok(ident) = crate_name("zng-wgt") {
71 match ident {
73 FoundCrate::Name(name) => (name, "__proc_macro_util"),
74 FoundCrate::Itself => ("zng_wgt".to_owned(), "__proc_macro_util"),
75 }
76 } else if let Ok(ident) = crate_name("zng-app") {
77 match ident {
79 FoundCrate::Name(name) => (name, ""),
80 FoundCrate::Itself => ("zng_app".to_owned(), ""),
81 }
82 } else {
83 ("zng".to_owned(), "__proc_macro_util")
85 }
86}
87
88#[derive(PartialEq, Debug)]
89enum FoundCrate {
90 Name(String),
91 Itself,
92}
93
94fn crate_name(orig_name: &str) -> Result<FoundCrate, ()> {
96 let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").map_err(|_| ())?);
97
98 let toml = fs::read_to_string(manifest_dir.join("Cargo.toml")).map_err(|_| ())?;
99
100 crate_name_impl(orig_name, &toml)
101}
102fn crate_name_impl(orig_name: &str, toml: &str) -> Result<FoundCrate, ()> {
103 enum State<'a> {
106 Seeking,
107 Package,
108 Dependencies,
109 Dependency(&'a str),
110 }
111
112 let mut state = State::Seeking;
113
114 for line in toml.lines() {
115 let line = line.trim();
116
117 let new_state = if line == "[package]" {
118 Some(State::Package)
119 } else if line.contains("dependencies.") && line.ends_with(']') {
120 let name_start = line.rfind('.').unwrap();
121 let name = line[name_start + 1..].trim_end_matches(']');
122 Some(State::Dependency(name))
123 } else if line.ends_with("dependencies]") {
124 Some(State::Dependencies)
125 } else if line.starts_with('[') {
126 Some(State::Seeking)
127 } else {
128 None
129 };
130
131 if let Some(new_state) = new_state {
132 if let State::Dependency(name) = state {
133 if name == orig_name {
134 return Ok(FoundCrate::Name(orig_name.replace('-', "_")));
136 }
137 }
138
139 state = new_state;
140 continue;
141 }
142
143 match state {
144 State::Seeking => continue,
145 State::Package => {
147 if line.starts_with("name ") || line.starts_with("name=") {
148 if let Some(name_start) = line.find('"') {
149 if let Some(name_end) = line.rfind('"') {
150 let name = &line[name_start + 1..name_end];
151
152 if name == orig_name {
153 return Ok(if env::var_os("CARGO_TARGET_TMPDIR").is_none() {
154 FoundCrate::Itself
155 } else {
156 FoundCrate::Name(orig_name.replace('-', "_"))
157 });
158 }
159 }
160 }
161 }
162 }
163 State::Dependencies => {
165 if let Some(eq) = line.find('=') {
166 let name = line[..eq].trim();
167 let value = line[eq + 1..].trim();
168
169 if value.starts_with('"') {
170 if name == orig_name {
171 return Ok(FoundCrate::Name(orig_name.replace('-', "_")));
172 }
173 } else if value.starts_with('{') {
174 let value = value.replace(' ', "");
175 if let Some(pkg) = value.find("package=\"") {
176 let pkg = &value[pkg + "package=\"".len()..];
177 if let Some(pkg_name_end) = pkg.find('"') {
178 let pkg_name = &pkg[..pkg_name_end];
179 if pkg_name == orig_name {
180 return Ok(FoundCrate::Name(name.replace('-', "_")));
181 }
182 }
183 } else if name == orig_name {
184 return Ok(FoundCrate::Name(orig_name.replace('-', "_")));
185 }
186 }
187 }
188 }
189 State::Dependency(name) => {
191 if line.starts_with("package ") || line.starts_with("package=") {
192 if let Some(pkg_name_start) = line.find('"') {
193 if let Some(pkg_name_end) = line.rfind('"') {
194 let pkg_name = &line[pkg_name_start + 1..pkg_name_end];
195
196 if pkg_name == orig_name {
197 return Ok(FoundCrate::Name(name.replace('-', "_")));
198 }
199 }
200 }
201 }
202 }
203 }
204 }
205
206 if let State::Dependency(name) = state {
207 if name == orig_name {
208 return Ok(FoundCrate::Name(orig_name.replace('-', "_")));
210 }
211 }
212
213 Err(())
214}
215
216macro_rules! abort {
218 ($span:expr, $($tt:tt)*) => {{
219 let error = format!($($tt)*);
220 let error = syn::LitStr::new(&error, proc_macro2::Span::call_site());
221
222 return quote_spanned!($span=> compile_error!{#error}).into();
223 }};
224}
225
226macro_rules! abort_call_site {
228 ($($tt:tt)*) => {
229 abort!(proc_macro2::Span::call_site(), $($tt)*)
230 };
231}
232
233macro_rules! non_user_error {
235 ($e:expr) => {
236 panic!("[{}:{}] invalid non-user input: {}", file!(), line!(), $e)
237 };
238 ($fmt:tt, $($args:tt)+) => {
239 non_user_error! {
240 format_args!($fmt, $($args)+)
241 }
242 }
243}
244
245macro_rules! non_user_group {
246 ($group_kind:ident, $input:expr) => {
247 {
248 fn inner(input: syn::parse::ParseStream) -> syn::Result<syn::parse::ParseBuffer> {
249 let inner;
250 syn::$group_kind!(inner in input);
252 Ok(inner)
253 }
254 inner($input).unwrap_or_else(|e| non_user_error!(e))
255 }
256 };
257 ($group_kind:ident, $input:expr, $ident:expr) => {
258 {
259 let id: syn::Ident = $input.parse().unwrap_or_else(|e| non_user_error!(e));
260 let ident = $ident;
261 if id != ident {
262 non_user_error!(format!("expected `{ident}`"));
263 }
264 non_user_group! { $group_kind, $input }
265 }
266 }
267}
268macro_rules! non_user_braced {
270 ($input:expr) => {
271 non_user_group! { braced, $input }
272 };
273 ($input:expr, $ident:expr) => {
274 non_user_group! { braced, $input, $ident }
275 };
276}
277
278#[allow(unused)] macro_rules! non_user_parenthesized {
281 ($input:expr) => {
282 non_user_group! { parenthesized, $input }
283 };
284}
285
286macro_rules! non_user_bracketed {
288 ($input:expr) => {
289 non_user_group! { bracketed, $input }
290 };
291}
292
293#[derive(Default)]
295pub struct Errors {
296 tokens: TokenStream,
297}
298impl Errors {
299 pub fn push(&mut self, error: impl ToString, span: Span) {
301 let error = error.to_string();
302 self.tokens.extend(quote_spanned! {span=>
303 compile_error!{#error}
304 })
305 }
306
307 pub fn push_syn(&mut self, error: syn::Error) {
309 for error in error {
310 let span = error.span();
311 let msg = error.to_string();
312 if msg != RECOVERABLE_TAG {
313 self.push(error, span);
314 }
315 }
316 }
317
318 pub fn is_empty(&self) -> bool {
319 self.tokens.is_empty()
320 }
321}
322impl ToTokens for Errors {
323 fn to_tokens(&self, tokens: &mut TokenStream) {
324 tokens.extend(self.tokens.clone())
325 }
326 fn to_token_stream(&self) -> TokenStream {
327 self.tokens.clone()
328 }
329 fn into_token_stream(self) -> TokenStream {
330 self.tokens
331 }
332}
333
334#[derive(Clone)]
336pub struct Attributes {
337 pub docs: Vec<Attribute>,
338 pub inline: Option<Attribute>,
339 pub cfg: Option<Attribute>,
340 pub deprecated: Option<Attribute>,
341 pub lints: Vec<Attribute>,
342 pub others: Vec<Attribute>,
343}
344impl Attributes {
345 pub fn new(attrs: Vec<Attribute>) -> Self {
346 let mut docs = vec![];
347 let mut inline = None;
348 let mut cfg = None;
349 let mut deprecated = None;
350 let mut lints = vec![];
351 let mut others = vec![];
352
353 for attr in attrs {
354 if let Some(ident) = attr.path().get_ident() {
355 if ident == "doc" {
356 docs.push(attr);
357 continue;
358 } else if ident == "inline" {
359 inline = Some(attr);
360 } else if ident == "cfg" {
361 cfg = Some(attr);
362 } else if ident == "deprecated" {
363 deprecated = Some(attr);
364 } else if ident == "allow" || ident == "expect" || ident == "warn" || ident == "deny" || ident == "forbid" {
365 lints.push(attr);
366 } else {
367 others.push(attr);
368 }
369 } else {
370 others.push(attr);
371 }
372 }
373
374 Attributes {
375 docs,
376 inline,
377 cfg,
378 deprecated,
379 lints,
380 others,
381 }
382 }
383
384 pub fn tag_doc(&mut self, text: &str, help: &str) {
386 let txt = format!("<strong title='{help}' data-tag='{text}'><code>{text}</code></strong> ");
387 for first in self.docs.iter_mut() {
388 match syn::parse2::<DocAttr>(first.tokens()) {
389 Ok(doc) => {
390 let mut msg = doc.msg.value();
391 msg.insert_str(0, &txt);
392 *first = parse_quote_spanned! {first.span()=>
393 #[doc = #msg]
394 };
395
396 return;
397 }
398 Err(_) => continue,
399 }
400 }
401 }
402
403 pub(crate) fn cfg_and_lints(&self) -> TokenStream {
404 let mut tts = self.cfg.to_token_stream();
405 for l in &self.lints {
406 l.to_tokens(&mut tts);
407 }
408 tts
409 }
410}
411impl ToTokens for Attributes {
412 fn to_tokens(&self, tokens: &mut TokenStream) {
413 for attr in self
414 .docs
415 .iter()
416 .chain(&self.inline)
417 .chain(&self.cfg)
418 .chain(&self.deprecated)
419 .chain(&self.lints)
420 .chain(&self.others)
421 {
422 attr.to_tokens(tokens);
423 }
424 }
425}
426
427struct DocAttr {
428 msg: LitStr,
429}
430impl Parse for DocAttr {
431 fn parse(input: ParseStream) -> syn::Result<Self> {
432 input.parse::<Token![=]>()?;
433 Ok(DocAttr { msg: input.parse()? })
434 }
435}
436
437pub fn display_path(path: &syn::Path) -> String {
439 path.to_token_stream().to_string().replace(' ', "")
440}
441
442pub fn path_span(path: &syn::Path) -> Span {
444 path.segments.last().map(|s| s.span()).unwrap_or_else(|| path.span())
445}
446
447struct OuterAttr {
448 pound_token: Token![#],
449 style: syn::AttrStyle,
450 bracket_token: syn::token::Bracket,
451 path: syn::Path,
452 tokens: TokenStream,
453}
454impl syn::parse::Parse for OuterAttr {
455 fn parse(input: ParseStream) -> syn::Result<Self> {
456 let inner;
457
458 Ok(OuterAttr {
459 pound_token: input.parse()?,
460 style: if input.peek(Token![!]) {
461 syn::AttrStyle::Inner(input.parse()?)
462 } else {
463 syn::AttrStyle::Outer
464 },
465 bracket_token: syn::bracketed!(inner in input),
466 path: inner.parse()?,
467 tokens: inner.parse()?,
468 })
469 }
470}
471impl From<OuterAttr> for Attribute {
472 fn from(s: OuterAttr) -> Self {
473 Attribute {
474 pound_token: s.pound_token,
475 style: s.style,
476 bracket_token: s.bracket_token,
477 meta: {
478 let path = s.path;
479 let tokens = s.tokens;
480 parse_quote!(#path #tokens)
481 },
482 }
483 }
484}
485
486pub fn format_rust_expr(value: String) -> String {
488 use std::io::Write;
490 use std::process::{Command, Stdio};
491 const PREFIX: &str = "const x:() = ";
492 const SUFFIX: &str = ";\n";
493 if let Ok(mut proc) = Command::new("rustfmt")
494 .arg("--emit=stdout")
495 .arg("--edition=2018")
496 .stdin(Stdio::piped())
497 .stdout(Stdio::piped())
498 .stderr(Stdio::null())
499 .spawn()
500 {
501 {
502 let stdin = proc.stdin.as_mut().unwrap();
503 stdin.write_all(PREFIX.as_bytes()).unwrap();
504 stdin.write_all(value.as_bytes()).unwrap();
505 stdin.write_all(SUFFIX.as_bytes()).unwrap();
506 }
507 if let Ok(output) = proc.wait_with_output() {
508 if output.status.success() {
509 let start = PREFIX.len() + 1;
512 let end = output.stdout.len() - SUFFIX.len();
513 return std::str::from_utf8(&output.stdout[start..end]).unwrap().to_owned();
514 }
515 }
516 }
517 value
518}
519
520pub fn last_span(tts: TokenStream) -> Span {
522 if let Some(tt) = tts.into_iter().last() {
523 if let proc_macro2::TokenTree::Group(g) = tt {
524 g.span_close()
525 } else {
526 tt.span()
527 }
528 } else {
529 Span::call_site()
530 }
531}
532
533#[derive(Clone, Copy, PartialEq, Eq)]
538pub enum LintLevel {
539 Allow,
540 Warn,
541 Deny,
542 Forbid,
543}
544impl fmt::Display for LintLevel {
545 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
546 match self {
547 LintLevel::Allow => write!(f, "allow_"),
548 LintLevel::Warn => write!(f, "warn_"),
549 LintLevel::Deny => write!(f, "deny_"),
550 LintLevel::Forbid => write!(f, "forbid_"),
551 }
552 }
553}
554
555pub fn take_zng_lints(
563 attrs: &mut Vec<Attribute>,
564 errors: &mut Errors,
565 forbidden: &std::collections::HashSet<&Ident>,
566) -> Vec<(Ident, LintLevel, Attribute)> {
567 let mut r = vec![];
568 let mut i = 0;
569 while i < attrs.len() {
570 if let Some(ident) = attrs[i].path().get_ident() {
571 let level = if ident == "allow_" {
572 LintLevel::Allow
573 } else if ident == "warn_" {
574 LintLevel::Warn
575 } else if ident == "deny_" {
576 LintLevel::Deny
577 } else if ident == "forbid_" {
578 LintLevel::Forbid
579 } else {
580 i += 1;
581 continue;
582 };
583 if let Ok(path) = syn::parse2::<LintPath>(attrs[i].tokens()) {
584 let path = path.path;
585 if path.segments.len() == 2 && path.segments[0].ident == "zng" {
586 let attr = attrs.remove(i);
587 let lint_ident = path.segments[1].ident.clone();
588 match level {
589 LintLevel::Warn => errors.push(
590 "cannot set zng lints to warn because warning diagnostics are not stable",
591 attr.path().span(),
592 ),
593 LintLevel::Allow if forbidden.contains(&lint_ident) => {
594 errors.push(format_args!("lint `zng::{lint_ident}` is `forbid` in this context"), attr.span())
595 }
596 _ => {
597 r.push((lint_ident, level, attr));
598 }
599 }
600
601 continue; }
603 }
604 }
605
606 i += 1;
607 }
608 r
609}
610struct LintPath {
611 _paren: syn::token::Paren,
612 path: syn::Path,
613}
614impl syn::parse::Parse for LintPath {
615 fn parse(input: ParseStream) -> syn::Result<Self> {
616 let inner;
617 Ok(LintPath {
618 _paren: syn::parenthesized!(inner in input),
619 path: inner.parse()?,
620 })
621 }
622}
623
624pub fn span_is_call_site(a: proc_macro2::Span) -> bool {
625 span_eq(a, proc_macro2::Span::call_site())
626}
627
628pub fn span_eq(a: proc_macro2::Span, b: proc_macro2::Span) -> bool {
629 format!("{a:?}") == format!("{b:?}")
630}
631
632pub fn parse_outer_attrs(input: ParseStream, errors: &mut Errors) -> Vec<Attribute> {
635 let mut attrs;
636 loop {
637 let fork = input.fork();
638 let mut parsed = true;
639
640 attrs = Attribute::parse_outer(&fork).unwrap_or_else(|e| {
641 parsed = false;
642 errors.push_syn(e);
643 vec![]
644 });
645 if parsed {
646 input.advance_to(&fork);
647 break;
648 } else {
649 let _ = input.parse::<Token![#]>();
650 if input.peek(Token![!]) {
651 let _ = input.parse::<Token![!]>();
652 }
653 let _ = non_user_bracketed!(input).parse::<TokenStream>();
654 }
655 }
656
657 attrs
658}
659
660pub fn recoverable_err(span: Span, msg: impl std::fmt::Display) -> syn::Error {
662 syn::Error::new(span, msg).set_recoverable()
663}
664
665const RECOVERABLE_TAG: &str = "<recoverable>";
666fn recoverable_tag() -> syn::Error {
667 syn::Error::new(Span::call_site(), RECOVERABLE_TAG)
668}
669
670pub trait ErrorRecoverable {
674 fn set_recoverable(self) -> Self;
676 fn recoverable(self) -> (bool, Self);
680}
681impl ErrorRecoverable for syn::Error {
682 fn set_recoverable(self) -> Self {
683 let mut errors = self.into_iter();
684 let mut e = errors.next().unwrap();
685
686 debug_assert!(e.to_string() != RECOVERABLE_TAG);
687
688 e.combine(recoverable_tag());
689
690 for error in errors {
691 if e.to_string() != RECOVERABLE_TAG {
692 e.combine(error);
693 e.combine(recoverable_tag());
694 }
695 }
696
697 e
698 }
699 fn recoverable(self) -> (bool, Self) {
700 let mut errors = self.into_iter();
701 let mut e = errors.next().unwrap();
702
703 debug_assert!(e.to_string() != RECOVERABLE_TAG);
704
705 let mut errors_count = 1;
706 let mut tags_count = 0;
707
708 for error in errors {
709 if error.to_string() == RECOVERABLE_TAG {
710 tags_count += 1;
711 } else {
712 errors_count += 1;
713 e.combine(error);
714 }
715 }
716
717 (errors_count == tags_count, e)
718 }
719}
720
721#[allow(unused)] #[cfg(debug_assertions)]
728pub mod debug_trace {
729 use std::sync::atomic::{AtomicBool, Ordering};
730
731 static ENABLED: AtomicBool = AtomicBool::new(false);
732
733 pub fn enable(enable: bool) {
734 let prev = ENABLED.swap(enable, Ordering::SeqCst);
735 if prev != enable {
736 eprintln!("zng-proc-macros::debug_trace {}", if enable { "enabled" } else { "disabled" });
737 }
738 }
739
740 pub fn display(msg: impl std::fmt::Display) {
741 if ENABLED.load(Ordering::SeqCst) {
742 eprintln!("{msg}");
743 }
744 }
745}
746
747#[allow(unused)] #[cfg(debug_assertions)]
749macro_rules! enable_trace {
750 () => {
751 $crate::util::debug_trace::enable(true);
752 };
753 (if $bool_expr:expr) => {
754 $crate::util::debug_trace::enable($bool_expr);
755 };
756}
757#[allow(unused)] #[cfg(debug_assertions)]
759macro_rules! trace {
760 ($msg:tt) => {
761 $crate::util::debug_trace::display($msg);
762 };
763 ($fmt:tt, $($args:tt)+) => {
764 $crate::util::debug_trace::display(format_args!($fmt, $($args)+));
765 };
766}
767
768pub fn parse_punct_terminated2<T: Parse, P: syn::token::Token + Parse>(input: TokenStream) -> syn::Result<Punctuated<T, P>> {
770 struct PunctTerm<T: Parse, P: syn::token::Token + Parse>(Punctuated<T, P>);
771
772 impl<T: Parse, P: syn::token::Token + Parse> Parse for PunctTerm<T, P> {
773 fn parse(input: ParseStream) -> syn::Result<Self> {
774 Ok(Self(Punctuated::parse_terminated(input)?))
775 }
776 }
777
778 syn::parse2::<PunctTerm<T, P>>(input).map(|p| p.0)
779}
780
781pub fn peek_any3(stream: ParseStream) -> bool {
783 let mut cursor = stream.cursor();
784
785 if let Some(group) = stream.cursor().group(Delimiter::None) {
786 cursor = group.0;
787 }
788
789 if let Some((_, cursor)) = cursor.token_tree() {
790 if let Some((_, cursor)) = cursor.token_tree() {
791 if let Some((_tt, _)) = cursor.token_tree() {
792 return true;
793 }
794 }
795 }
796
797 false
798}
799
800pub fn set_stream_span(stream: TokenStream, span: Span) -> TokenStream {
802 stream
803 .into_iter()
804 .map(|mut tt| {
805 tt.set_span(span);
806 tt
807 })
808 .collect()
809}
810
811pub trait AttributeExt {
812 fn tokens(&self) -> TokenStream;
813}
814impl AttributeExt for Attribute {
815 fn tokens(&self) -> TokenStream {
816 match &self.meta {
817 syn::Meta::Path(_) => quote!(),
818 syn::Meta::List(m) => {
819 let t = &m.tokens;
820 match &m.delimiter {
821 syn::MacroDelimiter::Paren(p) => quote_spanned!(p.span.join()=> (#t)),
822 syn::MacroDelimiter::Brace(b) => quote_spanned!(b.span.join()=> {#t}),
823 syn::MacroDelimiter::Bracket(b) => quote_spanned!(b.span.join()=> [#t]),
824 }
825 }
826 syn::Meta::NameValue(m) => {
827 let eq = &m.eq_token;
828 let tk = &m.value;
829 quote!(#eq #tk)
830 }
831 }
832 }
833}
834
835#[cfg(test)]
836mod tests {
837 use super::*;
838
839 #[test]
840 fn crate_name_itself_1() {
841 let toml = r#"
842 [package]
843 name = "crate-name"
844 version = "0.1.0"
845 edition = "2024"
846 license = "Apache-2.0"
847 "#;
848
849 let r = crate_name_impl("crate-name", toml).unwrap();
850 assert_eq!(FoundCrate::Itself, r);
851 }
852
853 #[test]
854 fn crate_name_itself_2() {
855 let toml = r#"
856 [package]
857 version = "0.1.0"
858 edition = "2024"
859 name = "crate-name"
860 license = "Apache-2.0"
861 "#;
862
863 let r = crate_name_impl("crate-name", toml).unwrap();
864 assert_eq!(FoundCrate::Itself, r);
865 }
866
867 #[test]
868 fn crate_name_dependencies_1() {
869 let toml = r#"
870 [package]
871 name = "foo"
872 version = "0.1.0"
873 edition = "2024"
874 license = "Apache-2.0"
875
876 [dependencies]
877 bar = "1.0"
878 crate-name = "*"
879
880 [workspace]
881 "#;
882
883 let r = crate_name_impl("crate-name", toml).unwrap();
884 assert_eq!(FoundCrate::Name("crate_name".to_owned()), r);
885 }
886
887 #[test]
888 fn crate_name_dependencies_2() {
889 let toml = r#"
890 [package]
891 name = "foo"
892 version = "0.1.0"
893 edition = "2024"
894 license = "Apache-2.0"
895
896 [dependencies]
897 zum = "1.0"
898 super-name = { version = "*", package = "crate-name" }
899
900 [workspace]
901 "#;
902
903 let r = crate_name_impl("crate-name", toml).unwrap();
904 assert_eq!(FoundCrate::Name("super_name".to_owned()), r);
905 }
906
907 #[test]
908 fn crate_name_dependencies_3() {
909 let toml = r#"
910 [package]
911 name = "foo"
912 version = "0.1.0"
913 edition = "2024"
914 license = "Apache-2.0"
915
916 [target.'cfg(windows)'.dependencies]
917 zum = "1.0"
918 super-name = { version = "*", package = "crate-name" }
919
920 [workspace]
921 "#;
922
923 let r = crate_name_impl("crate-name", toml).unwrap();
924 assert_eq!(FoundCrate::Name("super_name".to_owned()), r);
925 }
926
927 #[test]
928 fn crate_name_dependencies_4() {
929 let toml = r#"
930 [package]
931 name = "foo"
932 version = "0.1.0"
933 edition = "2024"
934 license = "Apache-2.0"
935
936 [dev-dependencies]
937 zum = "1.0"
938 super-name = { version = "*", package = "crate-name" }
939
940 [workspace]
941 "#;
942
943 let r = crate_name_impl("crate-name", toml).unwrap();
944 assert_eq!(FoundCrate::Name("super_name".to_owned()), r);
945 }
946
947 #[test]
948 fn crate_name_dependency_1() {
949 let toml = r#"
950 [package]
951 name = "foo"
952 version = "0.1.0"
953 edition = "2024"
954 license = "Apache-2.0"
955
956 [dev-dependencies.super-foo]
957 version = "*"
958 package = "crate-name"
959
960 [workspace]
961 "#;
962
963 let r = crate_name_impl("crate-name", toml).unwrap();
964 assert_eq!(FoundCrate::Name("super_foo".to_owned()), r);
965 }
966
967 #[test]
968 fn crate_name_dependency_2() {
969 let toml = r#"
970 [package]
971 name = "foo"
972 version = "0.1.0"
973 edition = "2024"
974 license = "Apache-2.0"
975
976 [dependencies.super-foo]
977 version = "*"
978 package = "crate-name"
979
980 [workspace]
981 "#;
982
983 let r = crate_name_impl("crate-name", toml).unwrap();
984 assert_eq!(FoundCrate::Name("super_foo".to_owned()), r);
985 }
986
987 #[test]
988 fn crate_name_dependency_3() {
989 let toml = r#"
990 [package]
991 name = "foo"
992 version = "0.1.0"
993 edition = "2024"
994 license = "Apache-2.0"
995
996 [dependencies.crate-name]
997 version = "*"
998
999 [workspace]
1000 "#;
1001
1002 let r = crate_name_impl("crate-name", toml).unwrap();
1003 assert_eq!(FoundCrate::Name("crate_name".to_owned()), r);
1004 }
1005}