1pub mod edit;
4pub mod edit_in_place;
5mod expr_ext;
6mod generated;
7pub mod make;
8mod node_ext;
9mod operators;
10pub mod prec;
11pub mod syntax_factory;
12mod token_ext;
13mod traits;
14
15use std::marker::PhantomData;
16
17use either::Either;
18
19use crate::{
20 SyntaxKind,
21 syntax_node::{SyntaxNode, SyntaxNodeChildren, SyntaxToken},
22};
23
24pub use self::{
25 expr_ext::{ArrayExprKind, BlockModifier, CallableExpr, ElseBranch, LiteralKind},
26 generated::{nodes::*, tokens::*},
27 node_ext::{
28 AttrKind, FieldKind, Macro, NameLike, NameOrNameRef, PathSegmentKind, SelfParamKind,
29 SlicePatComponents, StructKind, TokenTreeChildren, TypeBoundKind, TypeOrConstParam,
30 VisibilityKind,
31 },
32 operators::{ArithOp, BinaryOp, CmpOp, LogicOp, Ordering, RangeOp, UnaryOp},
33 token_ext::{
34 AnyString, CommentKind, CommentPlacement, CommentShape, IsString, QuoteOffsets, Radix,
35 },
36 traits::{
37 AttrDocCommentIter, DocCommentIter, HasArgList, HasAttrs, HasDocComments, HasGenericArgs,
38 HasGenericParams, HasLoopBody, HasModuleItem, HasName, HasTypeBounds, HasVisibility,
39 attrs_including_inner,
40 },
41};
42
43pub trait AstNode {
48 fn kind() -> SyntaxKind
50 where
51 Self: Sized,
52 {
53 panic!("dynamic `SyntaxKind` for `AstNode::kind()`")
54 }
55
56 fn can_cast(kind: SyntaxKind) -> bool
57 where
58 Self: Sized;
59
60 fn cast(syntax: SyntaxNode) -> Option<Self>
61 where
62 Self: Sized;
63
64 fn syntax(&self) -> &SyntaxNode;
65 fn clone_for_update(&self) -> Self
66 where
67 Self: Sized,
68 {
69 Self::cast(self.syntax().clone_for_update()).unwrap()
70 }
71 fn clone_subtree(&self) -> Self
72 where
73 Self: Sized,
74 {
75 Self::cast(self.syntax().clone_subtree()).unwrap()
76 }
77}
78
79pub trait AstToken {
81 fn can_cast(token: SyntaxKind) -> bool
82 where
83 Self: Sized;
84
85 fn cast(syntax: SyntaxToken) -> Option<Self>
86 where
87 Self: Sized;
88
89 fn syntax(&self) -> &SyntaxToken;
90
91 fn text(&self) -> &str {
92 self.syntax().text()
93 }
94}
95
96#[derive(Debug, Clone)]
98pub struct AstChildren<N> {
99 inner: SyntaxNodeChildren,
100 ph: PhantomData<N>,
101}
102
103impl<N> AstChildren<N> {
104 fn new(parent: &SyntaxNode) -> Self {
105 AstChildren { inner: parent.children(), ph: PhantomData }
106 }
107}
108
109impl<N: AstNode> Iterator for AstChildren<N> {
110 type Item = N;
111 fn next(&mut self) -> Option<N> {
112 self.inner.find_map(N::cast)
113 }
114}
115
116impl<L, R> AstNode for Either<L, R>
117where
118 L: AstNode,
119 R: AstNode,
120{
121 fn can_cast(kind: SyntaxKind) -> bool
122 where
123 Self: Sized,
124 {
125 L::can_cast(kind) || R::can_cast(kind)
126 }
127
128 fn cast(syntax: SyntaxNode) -> Option<Self>
129 where
130 Self: Sized,
131 {
132 if L::can_cast(syntax.kind()) {
133 L::cast(syntax).map(Either::Left)
134 } else {
135 R::cast(syntax).map(Either::Right)
136 }
137 }
138
139 fn syntax(&self) -> &SyntaxNode {
140 self.as_ref().either(L::syntax, R::syntax)
141 }
142}
143
144impl<L, R> HasAttrs for Either<L, R>
145where
146 L: HasAttrs,
147 R: HasAttrs,
148{
149}
150
151pub trait RangeItem {
153 type Bound;
154
155 fn start(&self) -> Option<Self::Bound>;
156 fn end(&self) -> Option<Self::Bound>;
157 fn op_kind(&self) -> Option<RangeOp>;
158 fn op_token(&self) -> Option<SyntaxToken>;
159}
160
161mod support {
162 use super::{AstChildren, AstNode, SyntaxKind, SyntaxNode, SyntaxToken};
163
164 #[inline]
165 pub(super) fn child<N: AstNode>(parent: &SyntaxNode) -> Option<N> {
166 parent.children().find_map(N::cast)
167 }
168
169 #[inline]
170 pub(super) fn children<N: AstNode>(parent: &SyntaxNode) -> AstChildren<N> {
171 AstChildren::new(parent)
172 }
173
174 #[inline]
175 pub(super) fn token(parent: &SyntaxNode, kind: SyntaxKind) -> Option<SyntaxToken> {
176 parent.children_with_tokens().filter_map(|it| it.into_token()).find(|it| it.kind() == kind)
177 }
178}
179
180#[test]
181fn assert_ast_is_dyn_compatible() {
182 fn _f(_: &dyn AstNode, _: &dyn HasName) {}
183}
184
185#[test]
186fn test_doc_comment_none() {
187 let file = SourceFile::parse(
188 r#"
189 // non-doc
190 mod foo {}
191 "#,
192 parser::Edition::CURRENT,
193 )
194 .ok()
195 .unwrap();
196 let module = file.syntax().descendants().find_map(Module::cast).unwrap();
197 assert!(module.doc_comments().doc_comment_text().is_none());
198}
199
200#[test]
201fn test_outer_doc_comment_of_items() {
202 let file = SourceFile::parse(
203 r#"
204 /// doc
205 // non-doc
206 mod foo {}
207 "#,
208 parser::Edition::CURRENT,
209 )
210 .ok()
211 .unwrap();
212 let module = file.syntax().descendants().find_map(Module::cast).unwrap();
213 assert_eq!(" doc", module.doc_comments().doc_comment_text().unwrap());
214}
215
216#[test]
217fn test_inner_doc_comment_of_items() {
218 let file = SourceFile::parse(
219 r#"
220 //! doc
221 // non-doc
222 mod foo {}
223 "#,
224 parser::Edition::CURRENT,
225 )
226 .ok()
227 .unwrap();
228 let module = file.syntax().descendants().find_map(Module::cast).unwrap();
229 assert!(module.doc_comments().doc_comment_text().is_none());
230}
231
232#[test]
233fn test_doc_comment_of_statics() {
234 let file = SourceFile::parse(
235 r#"
236 /// Number of levels
237 static LEVELS: i32 = 0;
238 "#,
239 parser::Edition::CURRENT,
240 )
241 .ok()
242 .unwrap();
243 let st = file.syntax().descendants().find_map(Static::cast).unwrap();
244 assert_eq!(" Number of levels", st.doc_comments().doc_comment_text().unwrap());
245}
246
247#[test]
248fn test_doc_comment_preserves_indents() {
249 let file = SourceFile::parse(
250 r#"
251 /// doc1
252 /// ```
253 /// fn foo() {
254 /// // ...
255 /// }
256 /// ```
257 mod foo {}
258 "#,
259 parser::Edition::CURRENT,
260 )
261 .ok()
262 .unwrap();
263 let module = file.syntax().descendants().find_map(Module::cast).unwrap();
264 assert_eq!(
265 " doc1\n ```\n fn foo() {\n // ...\n }\n ```",
266 module.doc_comments().doc_comment_text().unwrap()
267 );
268}
269
270#[test]
271fn test_doc_comment_preserves_newlines() {
272 let file = SourceFile::parse(
273 r#"
274 /// this
275 /// is
276 /// mod
277 /// foo
278 mod foo {}
279 "#,
280 parser::Edition::CURRENT,
281 )
282 .ok()
283 .unwrap();
284 let module = file.syntax().descendants().find_map(Module::cast).unwrap();
285 assert_eq!(" this\n is\n mod\n foo", module.doc_comments().doc_comment_text().unwrap());
286}
287
288#[test]
289fn test_doc_comment_single_line_block_strips_suffix() {
290 let file = SourceFile::parse(
291 r#"
292 /** this is mod foo*/
293 mod foo {}
294 "#,
295 parser::Edition::CURRENT,
296 )
297 .ok()
298 .unwrap();
299 let module = file.syntax().descendants().find_map(Module::cast).unwrap();
300 assert_eq!(" this is mod foo", module.doc_comments().doc_comment_text().unwrap());
301}
302
303#[test]
304fn test_doc_comment_single_line_block_strips_suffix_whitespace() {
305 let file = SourceFile::parse(
306 r#"
307 /** this is mod foo */
308 mod foo {}
309 "#,
310 parser::Edition::CURRENT,
311 )
312 .ok()
313 .unwrap();
314 let module = file.syntax().descendants().find_map(Module::cast).unwrap();
315 assert_eq!(" this is mod foo ", module.doc_comments().doc_comment_text().unwrap());
316}
317
318#[test]
319fn test_doc_comment_multi_line_block_strips_suffix() {
320 let file = SourceFile::parse(
321 r#"
322 /**
323 this
324 is
325 mod foo
326 */
327 mod foo {}
328 "#,
329 parser::Edition::CURRENT,
330 )
331 .ok()
332 .unwrap();
333 let module = file.syntax().descendants().find_map(Module::cast).unwrap();
334 assert_eq!(
335 "\n this\n is\n mod foo\n ",
336 module.doc_comments().doc_comment_text().unwrap()
337 );
338}
339
340#[test]
341fn test_comments_preserve_trailing_whitespace() {
342 let file = SourceFile::parse(
343 "\n/// Representation of a Realm. \n/// In the specification these are called Realm Records.\nstruct Realm {}", parser::Edition::CURRENT,
344 )
345 .ok()
346 .unwrap();
347 let def = file.syntax().descendants().find_map(Struct::cast).unwrap();
348 assert_eq!(
349 " Representation of a Realm. \n In the specification these are called Realm Records.",
350 def.doc_comments().doc_comment_text().unwrap()
351 );
352}
353
354#[test]
355fn test_four_slash_line_comment() {
356 let file = SourceFile::parse(
357 r#"
358 //// too many slashes to be a doc comment
359 /// doc comment
360 mod foo {}
361 "#,
362 parser::Edition::CURRENT,
363 )
364 .ok()
365 .unwrap();
366 let module = file.syntax().descendants().find_map(Module::cast).unwrap();
367 assert_eq!(" doc comment", module.doc_comments().doc_comment_text().unwrap());
368}
369
370#[test]
371fn test_where_predicates() {
372 fn assert_bound(text: &str, bound: Option<TypeBound>) {
373 assert_eq!(text, bound.unwrap().syntax().text().to_string());
374 }
375
376 let file = SourceFile::parse(
377 r#"
378fn foo()
379where
380 T: Clone + Copy + Debug + 'static,
381 'a: 'b + 'c,
382 Iterator::Item: 'a + Debug,
383 Iterator::Item: Debug + 'a,
384 <T as Iterator>::Item: Debug + 'a,
385 for<'a> F: Fn(&'a str)
386{}
387 "#,
388 parser::Edition::CURRENT,
389 )
390 .ok()
391 .unwrap();
392 let where_clause = file.syntax().descendants().find_map(WhereClause::cast).unwrap();
393
394 let mut predicates = where_clause.predicates();
395
396 let pred = predicates.next().unwrap();
397 let mut bounds = pred.type_bound_list().unwrap().bounds();
398
399 assert!(pred.for_binder().is_none());
400 assert_eq!("T", pred.ty().unwrap().syntax().text().to_string());
401 assert_bound("Clone", bounds.next());
402 assert_bound("Copy", bounds.next());
403 assert_bound("Debug", bounds.next());
404 assert_bound("'static", bounds.next());
405
406 let pred = predicates.next().unwrap();
407 let mut bounds = pred.type_bound_list().unwrap().bounds();
408
409 assert_eq!("'a", pred.lifetime().unwrap().lifetime_ident_token().unwrap().text());
410
411 assert_bound("'b", bounds.next());
412 assert_bound("'c", bounds.next());
413
414 let pred = predicates.next().unwrap();
415 let mut bounds = pred.type_bound_list().unwrap().bounds();
416
417 assert_eq!("Iterator::Item", pred.ty().unwrap().syntax().text().to_string());
418 assert_bound("'a", bounds.next());
419
420 let pred = predicates.next().unwrap();
421 let mut bounds = pred.type_bound_list().unwrap().bounds();
422
423 assert_eq!("Iterator::Item", pred.ty().unwrap().syntax().text().to_string());
424 assert_bound("Debug", bounds.next());
425 assert_bound("'a", bounds.next());
426
427 let pred = predicates.next().unwrap();
428 let mut bounds = pred.type_bound_list().unwrap().bounds();
429
430 assert_eq!("<T as Iterator>::Item", pred.ty().unwrap().syntax().text().to_string());
431 assert_bound("Debug", bounds.next());
432 assert_bound("'a", bounds.next());
433
434 let pred = predicates.next().unwrap();
435 let mut bounds = pred.type_bound_list().unwrap().bounds();
436
437 assert_eq!(
438 "<'a>",
439 pred.for_binder().unwrap().generic_param_list().unwrap().syntax().text().to_string()
440 );
441 assert_eq!("F", pred.ty().unwrap().syntax().text().to_string());
442 assert_bound("Fn(&'a str)", bounds.next());
443}