Skip to main content

rustidy_ast/
path.rs

1//! Path
2
3// Imports
4use {
5	super::token,
6	core::fmt::Debug,
7	either::Either,
8	rustidy_ast_util::{Identifier, Punctuated, punct},
9	rustidy_format::{Format, Formattable, WhitespaceFormat},
10	rustidy_parse::Parse,
11	rustidy_print::Print,
12	rustidy_util::Whitespace,
13	std::borrow::Cow,
14};
15
16/// `SimplePath`
17#[derive(PartialEq, Eq, Clone, Debug)]
18#[derive(serde::Serialize, serde::Deserialize)]
19#[derive(Parse, Formattable, Format, Print)]
20#[parse(name = "a simple path")]
21pub struct SimplePath {
22	pub prefix:   Option<token::PathSep>,
23	#[format(prefix_ws(expr = Whitespace::REMOVE, if_ = self.prefix.is_some()))]
24	#[format(args = punct::fmt(Whitespace::REMOVE, Whitespace::REMOVE))]
25	pub segments: Punctuated<SimplePathSegment, token::PathSep>,
26}
27
28impl SimplePath {
29	/// Returns this path as a string
30	#[must_use]
31	pub fn as_str(&self) -> Cow<'_, str> {
32		// Optimize single segment paths with no prefix first
33		if self.prefix.is_none() && self.segments.rest.is_empty() {
34			return self.segments.first.as_str();
35		}
36
37		// TODO: Optimize this
38		let mut output = String::new();
39		if self.prefix.is_some() {
40			output.push_str("::");
41		}
42		for segment in self.segments.iter() {
43			match segment {
44				Either::Left(segment) => output.push_str(&segment.as_str()),
45				Either::Right(_) => output.push_str("::"),
46			}
47		}
48
49		output.into()
50	}
51
52	/// Returns if the first segment of this path is `segment`
53	#[must_use]
54	pub fn starts_with(&self, segment: &str) -> bool {
55		// TODO: Should we care about the prefix here?
56		self.segments.first.is_str(segment)
57	}
58
59	/// Returns if this path is the same as `path`.
60	#[must_use]
61	pub fn is_str(&self, path: &str) -> bool {
62		// Check for prefix
63		let (path, has_prefix) = match path.strip_prefix("::") {
64			Some(path) => (path, true),
65			None => (path, false),
66		};
67		if self.prefix.is_some() != has_prefix {
68			return false;
69		}
70
71		// Then check each segment
72		let mut lhs_segments = self.segments.values();
73		let mut rhs_segments = path.split("::");
74		loop {
75			match (lhs_segments.next(), rhs_segments.next()) {
76				(None, None) => break,
77				(None, Some(_)) | (Some(_), None) => return false,
78				(Some(lhs), Some(rhs)) => if !lhs.is_str(rhs) {
79					return false;
80				},
81			}
82		}
83
84		true
85	}
86}
87
88/// `SimplePathSegment`
89#[derive(PartialEq, Eq, Clone, Debug)]
90#[derive(strum::EnumTryAs)]
91#[derive(serde::Serialize, serde::Deserialize)]
92#[derive(Parse, Formattable, Format, Print)]
93pub enum SimplePathSegment {
94	Super(token::Super),
95	SelfLower(token::SelfLower),
96	Crate(token::Crate),
97	DollarCrate(token::DollarCrate),
98	Ident(Identifier),
99}
100
101impl SimplePathSegment {
102	/// Returns this path as a string
103	#[must_use]
104	pub fn as_str(&self) -> Cow<'_, str> {
105		match self {
106			Self::Super(_) => "super".into(),
107			Self::SelfLower(_) => "self".into(),
108			Self::Crate(_) => "crate".into(),
109			Self::DollarCrate(_) => "$crate".into(),
110			Self::Ident(ident) => ident.as_str(),
111		}
112	}
113
114	/// Returns if this segment is the same as `segment`.
115	#[must_use]
116	pub fn is_str(&self, segment: &str) -> bool {
117		match self {
118			Self::Super(_) => segment == "super",
119			Self::SelfLower(_) => segment == "self",
120			Self::Crate(_) => segment == "crate",
121			Self::DollarCrate(_) => segment == "$crate",
122			Self::Ident(ident) => ident.is_str(segment),
123		}
124	}
125}