aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_analysis/src/imp.rs12
-rw-r--r--crates/ra_editor/src/assists.rs34
-rw-r--r--crates/ra_editor/src/assists/add_derive.rs97
-rw-r--r--crates/ra_editor/src/assists/add_impl.rs78
-rw-r--r--crates/ra_editor/src/assists/change_visibility.rs90
-rw-r--r--crates/ra_editor/src/assists/flip_comma.rs45
-rw-r--r--crates/ra_editor/src/assists/introduce_variable.rs156
-rw-r--r--crates/ra_editor/src/code_actions.rs415
-rw-r--r--crates/ra_editor/src/lib.rs6
-rw-r--r--crates/ra_syntax/src/ast.rs6
-rw-r--r--crates/ra_syntax/src/ast/generated.rs47
-rw-r--r--crates/ra_syntax/src/grammar.ron15
12 files changed, 573 insertions, 428 deletions
diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs
index b812c3441..136e7f7dc 100644
--- a/crates/ra_analysis/src/imp.rs
+++ b/crates/ra_analysis/src/imp.rs
@@ -6,7 +6,7 @@ use hir::{
6 self, FnSignatureInfo, Problem, source_binder, 6 self, FnSignatureInfo, Problem, source_binder,
7}; 7};
8use ra_db::{FilesDatabase, SourceRoot, SourceRootId, SyntaxDatabase}; 8use ra_db::{FilesDatabase, SourceRoot, SourceRootId, SyntaxDatabase};
9use ra_editor::{self, find_node_at_offset, LocalEdit, Severity}; 9use ra_editor::{self, find_node_at_offset, assists, LocalEdit, Severity};
10use ra_syntax::{ 10use ra_syntax::{
11 algo::{find_covering_node, visit::{visitor, Visitor}}, 11 algo::{find_covering_node, visit::{visitor, Visitor}},
12 ast::{self, ArgListOwner, Expr, FnDef, NameOwner}, 12 ast::{self, ArgListOwner, Expr, FnDef, NameOwner},
@@ -335,11 +335,11 @@ impl db::RootDatabase {
335 let file = self.source_file(frange.file_id); 335 let file = self.source_file(frange.file_id);
336 let offset = frange.range.start(); 336 let offset = frange.range.start();
337 let actions = vec![ 337 let actions = vec![
338 ra_editor::flip_comma(&file, offset).map(|f| f()), 338 assists::flip_comma(&file, offset).map(|f| f()),
339 ra_editor::add_derive(&file, offset).map(|f| f()), 339 assists::add_derive(&file, offset).map(|f| f()),
340 ra_editor::add_impl(&file, offset).map(|f| f()), 340 assists::add_impl(&file, offset).map(|f| f()),
341 ra_editor::make_pub_crate(&file, offset).map(|f| f()), 341 assists::change_visibility(&file, offset).map(|f| f()),
342 ra_editor::introduce_variable(&file, frange.range).map(|f| f()), 342 assists::introduce_variable(&file, frange.range).map(|f| f()),
343 ]; 343 ];
344 actions 344 actions
345 .into_iter() 345 .into_iter()
diff --git a/crates/ra_editor/src/assists.rs b/crates/ra_editor/src/assists.rs
new file mode 100644
index 000000000..b6e6dd628
--- /dev/null
+++ b/crates/ra_editor/src/assists.rs
@@ -0,0 +1,34 @@
1//! This modules contains various "assits": suggestions for source code edits
2//! which are likely to occur at a given cursor positon. For example, if the
3//! cursor is on the `,`, a possible assist is swapping the elments around the
4//! comma.
5
6mod flip_comma;
7mod add_derive;
8mod add_impl;
9mod introduce_variable;
10mod change_visibility;
11
12use ra_text_edit::TextEdit;
13use ra_syntax::{Direction, SyntaxNodeRef, TextUnit};
14
15pub use self::{
16 flip_comma::flip_comma,
17 add_derive::add_derive,
18 add_impl::add_impl,
19 introduce_variable::introduce_variable,
20 change_visibility::change_visibility,
21};
22
23#[derive(Debug)]
24pub struct LocalEdit {
25 pub label: String,
26 pub edit: TextEdit,
27 pub cursor_position: Option<TextUnit>,
28}
29
30fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<SyntaxNodeRef> {
31 node.siblings(direction)
32 .skip(1)
33 .find(|node| !node.kind().is_trivia())
34}
diff --git a/crates/ra_editor/src/assists/add_derive.rs b/crates/ra_editor/src/assists/add_derive.rs
new file mode 100644
index 000000000..33d9d2c31
--- /dev/null
+++ b/crates/ra_editor/src/assists/add_derive.rs
@@ -0,0 +1,97 @@
1use ra_text_edit::TextEditBuilder;
2use ra_syntax::{
3 ast::{self, AstNode, AttrsOwner},
4 SourceFileNode,
5 SyntaxKind::{WHITESPACE, COMMENT},
6 TextUnit,
7};
8
9use crate::{
10 find_node_at_offset,
11 assists::LocalEdit,
12};
13
14pub fn add_derive<'a>(
15 file: &'a SourceFileNode,
16 offset: TextUnit,
17) -> Option<impl FnOnce() -> LocalEdit + 'a> {
18 let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?;
19 let node_start = derive_insertion_offset(nominal)?;
20 return Some(move || {
21 let derive_attr = nominal
22 .attrs()
23 .filter_map(|x| x.as_call())
24 .filter(|(name, _arg)| name == "derive")
25 .map(|(_name, arg)| arg)
26 .next();
27 let mut edit = TextEditBuilder::new();
28 let offset = match derive_attr {
29 None => {
30 edit.insert(node_start, "#[derive()]\n".to_string());
31 node_start + TextUnit::of_str("#[derive(")
32 }
33 Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'),
34 };
35 LocalEdit {
36 label: "add `#[derive]`".to_string(),
37 edit: edit.finish(),
38 cursor_position: Some(offset),
39 }
40 });
41
42 // Insert `derive` after doc comments.
43 fn derive_insertion_offset(nominal: ast::NominalDef) -> Option<TextUnit> {
44 let non_ws_child = nominal
45 .syntax()
46 .children()
47 .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?;
48 Some(non_ws_child.range().start())
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55 use crate::test_utils::check_action;
56
57 #[test]
58 fn add_derive_new() {
59 check_action(
60 "struct Foo { a: i32, <|>}",
61 "#[derive(<|>)]\nstruct Foo { a: i32, }",
62 |file, off| add_derive(file, off).map(|f| f()),
63 );
64 check_action(
65 "struct Foo { <|> a: i32, }",
66 "#[derive(<|>)]\nstruct Foo { a: i32, }",
67 |file, off| add_derive(file, off).map(|f| f()),
68 );
69 }
70
71 #[test]
72 fn add_derive_existing() {
73 check_action(
74 "#[derive(Clone)]\nstruct Foo { a: i32<|>, }",
75 "#[derive(Clone<|>)]\nstruct Foo { a: i32, }",
76 |file, off| add_derive(file, off).map(|f| f()),
77 );
78 }
79
80 #[test]
81 fn add_derive_new_with_doc_comment() {
82 check_action(
83 "
84/// `Foo` is a pretty important struct.
85/// It does stuff.
86struct Foo { a: i32<|>, }
87 ",
88 "
89/// `Foo` is a pretty important struct.
90/// It does stuff.
91#[derive(<|>)]
92struct Foo { a: i32, }
93 ",
94 |file, off| add_derive(file, off).map(|f| f()),
95 );
96 }
97}
diff --git a/crates/ra_editor/src/assists/add_impl.rs b/crates/ra_editor/src/assists/add_impl.rs
new file mode 100644
index 000000000..50e00688e
--- /dev/null
+++ b/crates/ra_editor/src/assists/add_impl.rs
@@ -0,0 +1,78 @@
1use join_to_string::join;
2use ra_text_edit::TextEditBuilder;
3use ra_syntax::{
4 ast::{self, AstNode, NameOwner, TypeParamsOwner},
5 SourceFileNode,
6 TextUnit,
7};
8
9use crate::{find_node_at_offset, assists::LocalEdit};
10
11pub fn add_impl<'a>(
12 file: &'a SourceFileNode,
13 offset: TextUnit,
14) -> Option<impl FnOnce() -> LocalEdit + 'a> {
15 let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?;
16 let name = nominal.name()?;
17
18 Some(move || {
19 let type_params = nominal.type_param_list();
20 let mut edit = TextEditBuilder::new();
21 let start_offset = nominal.syntax().range().end();
22 let mut buf = String::new();
23 buf.push_str("\n\nimpl");
24 if let Some(type_params) = type_params {
25 type_params.syntax().text().push_to(&mut buf);
26 }
27 buf.push_str(" ");
28 buf.push_str(name.text().as_str());
29 if let Some(type_params) = type_params {
30 let lifetime_params = type_params
31 .lifetime_params()
32 .filter_map(|it| it.lifetime())
33 .map(|it| it.text());
34 let type_params = type_params
35 .type_params()
36 .filter_map(|it| it.name())
37 .map(|it| it.text());
38 join(lifetime_params.chain(type_params))
39 .surround_with("<", ">")
40 .to_buf(&mut buf);
41 }
42 buf.push_str(" {\n");
43 let offset = start_offset + TextUnit::of_str(&buf);
44 buf.push_str("\n}");
45 edit.insert(start_offset, buf);
46 LocalEdit {
47 label: "add impl".to_string(),
48 edit: edit.finish(),
49 cursor_position: Some(offset),
50 }
51 })
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57 use crate::test_utils::check_action;
58
59 #[test]
60 fn test_add_impl() {
61 check_action(
62 "struct Foo {<|>}\n",
63 "struct Foo {}\n\nimpl Foo {\n<|>\n}\n",
64 |file, off| add_impl(file, off).map(|f| f()),
65 );
66 check_action(
67 "struct Foo<T: Clone> {<|>}",
68 "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}",
69 |file, off| add_impl(file, off).map(|f| f()),
70 );
71 check_action(
72 "struct Foo<'a, T: Foo<'a>> {<|>}",
73 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}",
74 |file, off| add_impl(file, off).map(|f| f()),
75 );
76 }
77
78}
diff --git a/crates/ra_editor/src/assists/change_visibility.rs b/crates/ra_editor/src/assists/change_visibility.rs
new file mode 100644
index 000000000..98c218f32
--- /dev/null
+++ b/crates/ra_editor/src/assists/change_visibility.rs
@@ -0,0 +1,90 @@
1use ra_text_edit::TextEditBuilder;
2use ra_syntax::{
3 SourceFileNode,
4 algo::find_leaf_at_offset,
5 SyntaxKind::{VISIBILITY, FN_KW, MOD_KW, STRUCT_KW, ENUM_KW, TRAIT_KW, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF},
6 TextUnit,
7};
8
9use crate::assists::LocalEdit;
10
11pub fn change_visibility<'a>(
12 file: &'a SourceFileNode,
13 offset: TextUnit,
14) -> Option<impl FnOnce() -> LocalEdit + 'a> {
15 let syntax = file.syntax();
16
17 let keyword = find_leaf_at_offset(syntax, offset).find(|leaf| match leaf.kind() {
18 FN_KW | MOD_KW | STRUCT_KW | ENUM_KW | TRAIT_KW => true,
19 _ => false,
20 })?;
21 let parent = keyword.parent()?;
22 let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF];
23 let node_start = parent.range().start();
24 Some(move || {
25 let mut edit = TextEditBuilder::new();
26
27 if !def_kws.iter().any(|&def_kw| def_kw == parent.kind())
28 || parent.children().any(|child| child.kind() == VISIBILITY)
29 {
30 return LocalEdit {
31 label: "make pub crate".to_string(),
32 edit: edit.finish(),
33 cursor_position: Some(offset),
34 };
35 }
36
37 edit.insert(node_start, "pub(crate) ".to_string());
38 LocalEdit {
39 label: "make pub crate".to_string(),
40 edit: edit.finish(),
41 cursor_position: Some(node_start),
42 }
43 })
44}
45
46#[cfg(test)]
47mod tests {
48 use super::*;
49 use crate::test_utils::check_action;
50
51 #[test]
52 fn test_change_visibility() {
53 check_action(
54 "<|>fn foo() {}",
55 "<|>pub(crate) fn foo() {}",
56 |file, off| change_visibility(file, off).map(|f| f()),
57 );
58 check_action(
59 "f<|>n foo() {}",
60 "<|>pub(crate) fn foo() {}",
61 |file, off| change_visibility(file, off).map(|f| f()),
62 );
63 check_action(
64 "<|>struct Foo {}",
65 "<|>pub(crate) struct Foo {}",
66 |file, off| change_visibility(file, off).map(|f| f()),
67 );
68 check_action("<|>mod foo {}", "<|>pub(crate) mod foo {}", |file, off| {
69 change_visibility(file, off).map(|f| f())
70 });
71 check_action(
72 "<|>trait Foo {}",
73 "<|>pub(crate) trait Foo {}",
74 |file, off| change_visibility(file, off).map(|f| f()),
75 );
76 check_action("m<|>od {}", "<|>pub(crate) mod {}", |file, off| {
77 change_visibility(file, off).map(|f| f())
78 });
79 check_action(
80 "pub(crate) f<|>n foo() {}",
81 "pub(crate) f<|>n foo() {}",
82 |file, off| change_visibility(file, off).map(|f| f()),
83 );
84 check_action(
85 "unsafe f<|>n foo() {}",
86 "<|>pub(crate) unsafe fn foo() {}",
87 |file, off| change_visibility(file, off).map(|f| f()),
88 );
89 }
90}
diff --git a/crates/ra_editor/src/assists/flip_comma.rs b/crates/ra_editor/src/assists/flip_comma.rs
new file mode 100644
index 000000000..d8727db0d
--- /dev/null
+++ b/crates/ra_editor/src/assists/flip_comma.rs
@@ -0,0 +1,45 @@
1use ra_text_edit::TextEditBuilder;
2use ra_syntax::{
3 algo::find_leaf_at_offset,
4 Direction, SourceFileNode,
5 SyntaxKind::COMMA,
6 TextUnit,
7};
8
9use crate::assists::{LocalEdit, non_trivia_sibling};
10
11pub fn flip_comma<'a>(
12 file: &'a SourceFileNode,
13 offset: TextUnit,
14) -> Option<impl FnOnce() -> LocalEdit + 'a> {
15 let syntax = file.syntax();
16
17 let comma = find_leaf_at_offset(syntax, offset).find(|leaf| leaf.kind() == COMMA)?;
18 let prev = non_trivia_sibling(comma, Direction::Prev)?;
19 let next = non_trivia_sibling(comma, Direction::Next)?;
20 Some(move || {
21 let mut edit = TextEditBuilder::new();
22 edit.replace(prev.range(), next.text().to_string());
23 edit.replace(next.range(), prev.text().to_string());
24 LocalEdit {
25 label: "flip comma".to_string(),
26 edit: edit.finish(),
27 cursor_position: None,
28 }
29 })
30}
31
32#[cfg(test)]
33mod tests {
34 use super::*;
35 use crate::test_utils::check_action;
36
37 #[test]
38 fn test_swap_comma() {
39 check_action(
40 "fn foo(x: i32,<|> y: Result<(), ()>) {}",
41 "fn foo(y: Result<(), ()>,<|> x: i32) {}",
42 |file, off| flip_comma(file, off).map(|f| f()),
43 )
44 }
45}
diff --git a/crates/ra_editor/src/assists/introduce_variable.rs b/crates/ra_editor/src/assists/introduce_variable.rs
new file mode 100644
index 000000000..17ab521fa
--- /dev/null
+++ b/crates/ra_editor/src/assists/introduce_variable.rs
@@ -0,0 +1,156 @@
1use ra_text_edit::TextEditBuilder;
2use ra_syntax::{
3 algo::{find_covering_node},
4 ast::{self, AstNode},
5 SourceFileNode,
6 SyntaxKind::{WHITESPACE},
7 SyntaxNodeRef, TextRange, TextUnit,
8};
9
10use crate::assists::LocalEdit;
11
12pub fn introduce_variable<'a>(
13 file: &'a SourceFileNode,
14 range: TextRange,
15) -> Option<impl FnOnce() -> LocalEdit + 'a> {
16 let node = find_covering_node(file.syntax(), range);
17 let expr = node.ancestors().filter_map(ast::Expr::cast).next()?;
18
19 let anchor_stmt = anchor_stmt(expr)?;
20 let indent = anchor_stmt.prev_sibling()?;
21 if indent.kind() != WHITESPACE {
22 return None;
23 }
24 return Some(move || {
25 let mut buf = String::new();
26 let mut edit = TextEditBuilder::new();
27
28 buf.push_str("let var_name = ");
29 expr.syntax().text().push_to(&mut buf);
30 let is_full_stmt = if let Some(expr_stmt) = ast::ExprStmt::cast(anchor_stmt) {
31 Some(expr.syntax()) == expr_stmt.expr().map(|e| e.syntax())
32 } else {
33 false
34 };
35 if is_full_stmt {
36 edit.replace(expr.syntax().range(), buf);
37 } else {
38 buf.push_str(";");
39 indent.text().push_to(&mut buf);
40 edit.replace(expr.syntax().range(), "var_name".to_string());
41 edit.insert(anchor_stmt.range().start(), buf);
42 }
43 let cursor_position = anchor_stmt.range().start() + TextUnit::of_str("let ");
44 LocalEdit {
45 label: "introduce variable".to_string(),
46 edit: edit.finish(),
47 cursor_position: Some(cursor_position),
48 }
49 });
50
51 /// Statement or last in the block expression, which will follow
52 /// the freshly introduced var.
53 fn anchor_stmt(expr: ast::Expr) -> Option<SyntaxNodeRef> {
54 expr.syntax().ancestors().find(|&node| {
55 if ast::Stmt::cast(node).is_some() {
56 return true;
57 }
58 if let Some(expr) = node
59 .parent()
60 .and_then(ast::Block::cast)
61 .and_then(|it| it.expr())
62 {
63 if expr.syntax() == node {
64 return true;
65 }
66 }
67 false
68 })
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use crate::test_utils::check_action_range;
76
77 #[test]
78 fn test_introduce_var_simple() {
79 check_action_range(
80 "
81fn foo() {
82 foo(<|>1 + 1<|>);
83}",
84 "
85fn foo() {
86 let <|>var_name = 1 + 1;
87 foo(var_name);
88}",
89 |file, range| introduce_variable(file, range).map(|f| f()),
90 );
91 }
92
93 #[test]
94 fn test_introduce_var_expr_stmt() {
95 check_action_range(
96 "
97fn foo() {
98 <|>1 + 1<|>;
99}",
100 "
101fn foo() {
102 let <|>var_name = 1 + 1;
103}",
104 |file, range| introduce_variable(file, range).map(|f| f()),
105 );
106 }
107
108 #[test]
109 fn test_introduce_var_part_of_expr_stmt() {
110 check_action_range(
111 "
112fn foo() {
113 <|>1<|> + 1;
114}",
115 "
116fn foo() {
117 let <|>var_name = 1;
118 var_name + 1;
119}",
120 |file, range| introduce_variable(file, range).map(|f| f()),
121 );
122 }
123
124 #[test]
125 fn test_introduce_var_last_expr() {
126 check_action_range(
127 "
128fn foo() {
129 bar(<|>1 + 1<|>)
130}",
131 "
132fn foo() {
133 let <|>var_name = 1 + 1;
134 bar(var_name)
135}",
136 |file, range| introduce_variable(file, range).map(|f| f()),
137 );
138 }
139
140 #[test]
141 fn test_introduce_var_last_full_expr() {
142 check_action_range(
143 "
144fn foo() {
145 <|>bar(1 + 1)<|>
146}",
147 "
148fn foo() {
149 let <|>var_name = bar(1 + 1);
150 var_name
151}",
152 |file, range| introduce_variable(file, range).map(|f| f()),
153 );
154 }
155
156}
diff --git a/crates/ra_editor/src/code_actions.rs b/crates/ra_editor/src/code_actions.rs
deleted file mode 100644
index 7615f37a6..000000000
--- a/crates/ra_editor/src/code_actions.rs
+++ /dev/null
@@ -1,415 +0,0 @@
1use join_to_string::join;
2
3use ra_syntax::{
4 algo::{find_covering_node, find_leaf_at_offset},
5 ast::{self, AstNode, AttrsOwner, NameOwner, TypeParamsOwner},
6 Direction, SourceFileNode,
7 SyntaxKind::{COMMA, WHITESPACE, COMMENT, VISIBILITY, FN_KW, MOD_KW, STRUCT_KW, ENUM_KW, TRAIT_KW, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF},
8 SyntaxNodeRef, TextRange, TextUnit,
9};
10
11use crate::{find_node_at_offset, TextEdit, TextEditBuilder};
12
13#[derive(Debug)]
14pub struct LocalEdit {
15 pub label: String,
16 pub edit: TextEdit,
17 pub cursor_position: Option<TextUnit>,
18}
19
20pub fn flip_comma<'a>(
21 file: &'a SourceFileNode,
22 offset: TextUnit,
23) -> Option<impl FnOnce() -> LocalEdit + 'a> {
24 let syntax = file.syntax();
25
26 let comma = find_leaf_at_offset(syntax, offset).find(|leaf| leaf.kind() == COMMA)?;
27 let prev = non_trivia_sibling(comma, Direction::Prev)?;
28 let next = non_trivia_sibling(comma, Direction::Next)?;
29 Some(move || {
30 let mut edit = TextEditBuilder::new();
31 edit.replace(prev.range(), next.text().to_string());
32 edit.replace(next.range(), prev.text().to_string());
33 LocalEdit {
34 label: "flip comma".to_string(),
35 edit: edit.finish(),
36 cursor_position: None,
37 }
38 })
39}
40
41pub fn add_derive<'a>(
42 file: &'a SourceFileNode,
43 offset: TextUnit,
44) -> Option<impl FnOnce() -> LocalEdit + 'a> {
45 let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?;
46 let node_start = derive_insertion_offset(nominal)?;
47 return Some(move || {
48 let derive_attr = nominal
49 .attrs()
50 .filter_map(|x| x.as_call())
51 .filter(|(name, _arg)| name == "derive")
52 .map(|(_name, arg)| arg)
53 .next();
54 let mut edit = TextEditBuilder::new();
55 let offset = match derive_attr {
56 None => {
57 edit.insert(node_start, "#[derive()]\n".to_string());
58 node_start + TextUnit::of_str("#[derive(")
59 }
60 Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'),
61 };
62 LocalEdit {
63 label: "add `#[derive]`".to_string(),
64 edit: edit.finish(),
65 cursor_position: Some(offset),
66 }
67 });
68
69 // Insert `derive` after doc comments.
70 fn derive_insertion_offset(nominal: ast::NominalDef) -> Option<TextUnit> {
71 let non_ws_child = nominal
72 .syntax()
73 .children()
74 .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?;
75 Some(non_ws_child.range().start())
76 }
77}
78
79pub fn add_impl<'a>(
80 file: &'a SourceFileNode,
81 offset: TextUnit,
82) -> Option<impl FnOnce() -> LocalEdit + 'a> {
83 let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?;
84 let name = nominal.name()?;
85
86 Some(move || {
87 let type_params = nominal.type_param_list();
88 let mut edit = TextEditBuilder::new();
89 let start_offset = nominal.syntax().range().end();
90 let mut buf = String::new();
91 buf.push_str("\n\nimpl");
92 if let Some(type_params) = type_params {
93 type_params.syntax().text().push_to(&mut buf);
94 }
95 buf.push_str(" ");
96 buf.push_str(name.text().as_str());
97 if let Some(type_params) = type_params {
98 let lifetime_params = type_params
99 .lifetime_params()
100 .filter_map(|it| it.lifetime())
101 .map(|it| it.text());
102 let type_params = type_params
103 .type_params()
104 .filter_map(|it| it.name())
105 .map(|it| it.text());
106 join(lifetime_params.chain(type_params))
107 .surround_with("<", ">")
108 .to_buf(&mut buf);
109 }
110 buf.push_str(" {\n");
111 let offset = start_offset + TextUnit::of_str(&buf);
112 buf.push_str("\n}");
113 edit.insert(start_offset, buf);
114 LocalEdit {
115 label: "add impl".to_string(),
116 edit: edit.finish(),
117 cursor_position: Some(offset),
118 }
119 })
120}
121
122pub fn introduce_variable<'a>(
123 file: &'a SourceFileNode,
124 range: TextRange,
125) -> Option<impl FnOnce() -> LocalEdit + 'a> {
126 let node = find_covering_node(file.syntax(), range);
127 let expr = node.ancestors().filter_map(ast::Expr::cast).next()?;
128
129 let anchor_stmt = anchor_stmt(expr)?;
130 let indent = anchor_stmt.prev_sibling()?;
131 if indent.kind() != WHITESPACE {
132 return None;
133 }
134 return Some(move || {
135 let mut buf = String::new();
136 let mut edit = TextEditBuilder::new();
137
138 buf.push_str("let var_name = ");
139 expr.syntax().text().push_to(&mut buf);
140 let is_full_stmt = if let Some(expr_stmt) = ast::ExprStmt::cast(anchor_stmt) {
141 Some(expr.syntax()) == expr_stmt.expr().map(|e| e.syntax())
142 } else {
143 false
144 };
145 if is_full_stmt {
146 edit.replace(expr.syntax().range(), buf);
147 } else {
148 buf.push_str(";");
149 indent.text().push_to(&mut buf);
150 edit.replace(expr.syntax().range(), "var_name".to_string());
151 edit.insert(anchor_stmt.range().start(), buf);
152 }
153 let cursor_position = anchor_stmt.range().start() + TextUnit::of_str("let ");
154 LocalEdit {
155 label: "introduce variable".to_string(),
156 edit: edit.finish(),
157 cursor_position: Some(cursor_position),
158 }
159 });
160
161 /// Statement or last in the block expression, which will follow
162 /// the freshly introduced var.
163 fn anchor_stmt(expr: ast::Expr) -> Option<SyntaxNodeRef> {
164 expr.syntax().ancestors().find(|&node| {
165 if ast::Stmt::cast(node).is_some() {
166 return true;
167 }
168 if let Some(expr) = node
169 .parent()
170 .and_then(ast::Block::cast)
171 .and_then(|it| it.expr())
172 {
173 if expr.syntax() == node {
174 return true;
175 }
176 }
177 false
178 })
179 }
180}
181
182pub fn make_pub_crate<'a>(
183 file: &'a SourceFileNode,
184 offset: TextUnit,
185) -> Option<impl FnOnce() -> LocalEdit + 'a> {
186 let syntax = file.syntax();
187
188 let keyword = find_leaf_at_offset(syntax, offset).find(|leaf| match leaf.kind() {
189 FN_KW | MOD_KW | STRUCT_KW | ENUM_KW | TRAIT_KW => true,
190 _ => false,
191 })?;
192 let parent = keyword.parent()?;
193 let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF];
194 let node_start = parent.range().start();
195 Some(move || {
196 let mut edit = TextEditBuilder::new();
197
198 if !def_kws.iter().any(|&def_kw| def_kw == parent.kind())
199 || parent.children().any(|child| child.kind() == VISIBILITY)
200 {
201 return LocalEdit {
202 label: "make pub crate".to_string(),
203 edit: edit.finish(),
204 cursor_position: Some(offset),
205 };
206 }
207
208 edit.insert(node_start, "pub(crate) ".to_string());
209 LocalEdit {
210 label: "make pub crate".to_string(),
211 edit: edit.finish(),
212 cursor_position: Some(node_start),
213 }
214 })
215}
216
217fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<SyntaxNodeRef> {
218 node.siblings(direction)
219 .skip(1)
220 .find(|node| !node.kind().is_trivia())
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226 use crate::test_utils::{check_action, check_action_range};
227
228 #[test]
229 fn test_swap_comma() {
230 check_action(
231 "fn foo(x: i32,<|> y: Result<(), ()>) {}",
232 "fn foo(y: Result<(), ()>,<|> x: i32) {}",
233 |file, off| flip_comma(file, off).map(|f| f()),
234 )
235 }
236
237 #[test]
238 fn add_derive_new() {
239 check_action(
240 "struct Foo { a: i32, <|>}",
241 "#[derive(<|>)]\nstruct Foo { a: i32, }",
242 |file, off| add_derive(file, off).map(|f| f()),
243 );
244 check_action(
245 "struct Foo { <|> a: i32, }",
246 "#[derive(<|>)]\nstruct Foo { a: i32, }",
247 |file, off| add_derive(file, off).map(|f| f()),
248 );
249 }
250
251 #[test]
252 fn add_derive_existing() {
253 check_action(
254 "#[derive(Clone)]\nstruct Foo { a: i32<|>, }",
255 "#[derive(Clone<|>)]\nstruct Foo { a: i32, }",
256 |file, off| add_derive(file, off).map(|f| f()),
257 );
258 }
259
260 #[test]
261 fn add_derive_new_with_doc_comment() {
262 check_action(
263 "
264/// `Foo` is a pretty important struct.
265/// It does stuff.
266struct Foo { a: i32<|>, }
267 ",
268 "
269/// `Foo` is a pretty important struct.
270/// It does stuff.
271#[derive(<|>)]
272struct Foo { a: i32, }
273 ",
274 |file, off| add_derive(file, off).map(|f| f()),
275 );
276 }
277
278 #[test]
279 fn test_add_impl() {
280 check_action(
281 "struct Foo {<|>}\n",
282 "struct Foo {}\n\nimpl Foo {\n<|>\n}\n",
283 |file, off| add_impl(file, off).map(|f| f()),
284 );
285 check_action(
286 "struct Foo<T: Clone> {<|>}",
287 "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}",
288 |file, off| add_impl(file, off).map(|f| f()),
289 );
290 check_action(
291 "struct Foo<'a, T: Foo<'a>> {<|>}",
292 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}",
293 |file, off| add_impl(file, off).map(|f| f()),
294 );
295 }
296
297 #[test]
298 fn test_introduce_var_simple() {
299 check_action_range(
300 "
301fn foo() {
302 foo(<|>1 + 1<|>);
303}",
304 "
305fn foo() {
306 let <|>var_name = 1 + 1;
307 foo(var_name);
308}",
309 |file, range| introduce_variable(file, range).map(|f| f()),
310 );
311 }
312
313 #[test]
314 fn test_introduce_var_expr_stmt() {
315 check_action_range(
316 "
317fn foo() {
318 <|>1 + 1<|>;
319}",
320 "
321fn foo() {
322 let <|>var_name = 1 + 1;
323}",
324 |file, range| introduce_variable(file, range).map(|f| f()),
325 );
326 }
327
328 #[test]
329 fn test_introduce_var_part_of_expr_stmt() {
330 check_action_range(
331 "
332fn foo() {
333 <|>1<|> + 1;
334}",
335 "
336fn foo() {
337 let <|>var_name = 1;
338 var_name + 1;
339}",
340 |file, range| introduce_variable(file, range).map(|f| f()),
341 );
342 }
343
344 #[test]
345 fn test_introduce_var_last_expr() {
346 check_action_range(
347 "
348fn foo() {
349 bar(<|>1 + 1<|>)
350}",
351 "
352fn foo() {
353 let <|>var_name = 1 + 1;
354 bar(var_name)
355}",
356 |file, range| introduce_variable(file, range).map(|f| f()),
357 );
358 }
359
360 #[test]
361 fn test_introduce_var_last_full_expr() {
362 check_action_range(
363 "
364fn foo() {
365 <|>bar(1 + 1)<|>
366}",
367 "
368fn foo() {
369 let <|>var_name = bar(1 + 1);
370 var_name
371}",
372 |file, range| introduce_variable(file, range).map(|f| f()),
373 );
374 }
375
376 #[test]
377 fn test_make_pub_crate() {
378 check_action(
379 "<|>fn foo() {}",
380 "<|>pub(crate) fn foo() {}",
381 |file, off| make_pub_crate(file, off).map(|f| f()),
382 );
383 check_action(
384 "f<|>n foo() {}",
385 "<|>pub(crate) fn foo() {}",
386 |file, off| make_pub_crate(file, off).map(|f| f()),
387 );
388 check_action(
389 "<|>struct Foo {}",
390 "<|>pub(crate) struct Foo {}",
391 |file, off| make_pub_crate(file, off).map(|f| f()),
392 );
393 check_action("<|>mod foo {}", "<|>pub(crate) mod foo {}", |file, off| {
394 make_pub_crate(file, off).map(|f| f())
395 });
396 check_action(
397 "<|>trait Foo {}",
398 "<|>pub(crate) trait Foo {}",
399 |file, off| make_pub_crate(file, off).map(|f| f()),
400 );
401 check_action("m<|>od {}", "<|>pub(crate) mod {}", |file, off| {
402 make_pub_crate(file, off).map(|f| f())
403 });
404 check_action(
405 "pub(crate) f<|>n foo() {}",
406 "pub(crate) f<|>n foo() {}",
407 |file, off| make_pub_crate(file, off).map(|f| f()),
408 );
409 check_action(
410 "unsafe f<|>n foo() {}",
411 "<|>pub(crate) unsafe fn foo() {}",
412 |file, off| make_pub_crate(file, off).map(|f| f()),
413 );
414 }
415}
diff --git a/crates/ra_editor/src/lib.rs b/crates/ra_editor/src/lib.rs
index bfc745e58..ac283e2e0 100644
--- a/crates/ra_editor/src/lib.rs
+++ b/crates/ra_editor/src/lib.rs
@@ -1,4 +1,4 @@
1mod code_actions; 1pub mod assists;
2mod extend_selection; 2mod extend_selection;
3mod folding_ranges; 3mod folding_ranges;
4mod line_index; 4mod line_index;
@@ -10,7 +10,7 @@ mod typing;
10mod diagnostics; 10mod diagnostics;
11 11
12pub use self::{ 12pub use self::{
13 code_actions::{add_derive, add_impl, flip_comma, introduce_variable, make_pub_crate, LocalEdit}, 13 assists::LocalEdit,
14 extend_selection::extend_selection, 14 extend_selection::extend_selection,
15 folding_ranges::{folding_ranges, Fold, FoldKind}, 15 folding_ranges::{folding_ranges, Fold, FoldKind},
16 line_index::{LineCol, LineIndex}, 16 line_index::{LineCol, LineIndex},
@@ -19,7 +19,7 @@ pub use self::{
19 typing::{join_lines, on_enter, on_eq_typed}, 19 typing::{join_lines, on_enter, on_eq_typed},
20 diagnostics::diagnostics 20 diagnostics::diagnostics
21}; 21};
22use ra_text_edit::{TextEdit, TextEditBuilder}; 22use ra_text_edit::TextEditBuilder;
23use ra_syntax::{ 23use ra_syntax::{
24 algo::find_leaf_at_offset, 24 algo::find_leaf_at_offset,
25 ast::{self, AstNode}, 25 ast::{self, AstNode},
diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs
index 3e948800e..e968c9728 100644
--- a/crates/ra_syntax/src/ast.rs
+++ b/crates/ra_syntax/src/ast.rs
@@ -30,6 +30,12 @@ pub trait NameOwner<'a>: AstNode<'a> {
30 } 30 }
31} 31}
32 32
33pub trait VisibilityOwner<'a>: AstNode<'a> {
34 fn visibility(self) -> Option<Visibility<'a>> {
35 child_opt(self)
36 }
37}
38
33pub trait LoopBodyOwner<'a>: AstNode<'a> { 39pub trait LoopBodyOwner<'a>: AstNode<'a> {
34 fn loop_body(self) -> Option<Block<'a>> { 40 fn loop_body(self) -> Option<Block<'a>> {
35 child_opt(self) 41 child_opt(self)
diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs
index c5ac90a62..c619fc130 100644
--- a/crates/ra_syntax/src/ast/generated.rs
+++ b/crates/ra_syntax/src/ast/generated.rs
@@ -695,6 +695,7 @@ impl<R: TreeRoot<RaTypes>> ConstDefNode<R> {
695} 695}
696 696
697 697
698impl<'a> ast::VisibilityOwner<'a> for ConstDef<'a> {}
698impl<'a> ast::NameOwner<'a> for ConstDef<'a> {} 699impl<'a> ast::NameOwner<'a> for ConstDef<'a> {}
699impl<'a> ast::TypeParamsOwner<'a> for ConstDef<'a> {} 700impl<'a> ast::TypeParamsOwner<'a> for ConstDef<'a> {}
700impl<'a> ast::AttrsOwner<'a> for ConstDef<'a> {} 701impl<'a> ast::AttrsOwner<'a> for ConstDef<'a> {}
@@ -810,6 +811,7 @@ impl<R: TreeRoot<RaTypes>> EnumDefNode<R> {
810} 811}
811 812
812 813
814impl<'a> ast::VisibilityOwner<'a> for EnumDef<'a> {}
813impl<'a> ast::NameOwner<'a> for EnumDef<'a> {} 815impl<'a> ast::NameOwner<'a> for EnumDef<'a> {}
814impl<'a> ast::TypeParamsOwner<'a> for EnumDef<'a> {} 816impl<'a> ast::TypeParamsOwner<'a> for EnumDef<'a> {}
815impl<'a> ast::AttrsOwner<'a> for EnumDef<'a> {} 817impl<'a> ast::AttrsOwner<'a> for EnumDef<'a> {}
@@ -1213,6 +1215,7 @@ impl<R: TreeRoot<RaTypes>> FnDefNode<R> {
1213} 1215}
1214 1216
1215 1217
1218impl<'a> ast::VisibilityOwner<'a> for FnDef<'a> {}
1216impl<'a> ast::NameOwner<'a> for FnDef<'a> {} 1219impl<'a> ast::NameOwner<'a> for FnDef<'a> {}
1217impl<'a> ast::TypeParamsOwner<'a> for FnDef<'a> {} 1220impl<'a> ast::TypeParamsOwner<'a> for FnDef<'a> {}
1218impl<'a> ast::AttrsOwner<'a> for FnDef<'a> {} 1221impl<'a> ast::AttrsOwner<'a> for FnDef<'a> {}
@@ -2136,6 +2139,7 @@ impl<R: TreeRoot<RaTypes>> ModuleNode<R> {
2136} 2139}
2137 2140
2138 2141
2142impl<'a> ast::VisibilityOwner<'a> for Module<'a> {}
2139impl<'a> ast::NameOwner<'a> for Module<'a> {} 2143impl<'a> ast::NameOwner<'a> for Module<'a> {}
2140impl<'a> ast::AttrsOwner<'a> for Module<'a> {} 2144impl<'a> ast::AttrsOwner<'a> for Module<'a> {}
2141impl<'a> ast::DocCommentsOwner<'a> for Module<'a> {} 2145impl<'a> ast::DocCommentsOwner<'a> for Module<'a> {}
@@ -2351,6 +2355,7 @@ impl<R: TreeRoot<RaTypes>> NamedFieldDefNode<R> {
2351} 2355}
2352 2356
2353 2357
2358impl<'a> ast::VisibilityOwner<'a> for NamedFieldDef<'a> {}
2354impl<'a> ast::NameOwner<'a> for NamedFieldDef<'a> {} 2359impl<'a> ast::NameOwner<'a> for NamedFieldDef<'a> {}
2355impl<'a> ast::AttrsOwner<'a> for NamedFieldDef<'a> {} 2360impl<'a> ast::AttrsOwner<'a> for NamedFieldDef<'a> {}
2356impl<'a> NamedFieldDef<'a> { 2361impl<'a> NamedFieldDef<'a> {
@@ -3082,6 +3087,7 @@ impl<R: TreeRoot<RaTypes>> PosFieldNode<R> {
3082} 3087}
3083 3088
3084 3089
3090impl<'a> ast::VisibilityOwner<'a> for PosField<'a> {}
3085impl<'a> ast::AttrsOwner<'a> for PosField<'a> {} 3091impl<'a> ast::AttrsOwner<'a> for PosField<'a> {}
3086impl<'a> PosField<'a> { 3092impl<'a> PosField<'a> {
3087 pub fn type_ref(self) -> Option<TypeRef<'a>> { 3093 pub fn type_ref(self) -> Option<TypeRef<'a>> {
@@ -3639,6 +3645,7 @@ impl<R: TreeRoot<RaTypes>> StaticDefNode<R> {
3639} 3645}
3640 3646
3641 3647
3648impl<'a> ast::VisibilityOwner<'a> for StaticDef<'a> {}
3642impl<'a> ast::NameOwner<'a> for StaticDef<'a> {} 3649impl<'a> ast::NameOwner<'a> for StaticDef<'a> {}
3643impl<'a> ast::TypeParamsOwner<'a> for StaticDef<'a> {} 3650impl<'a> ast::TypeParamsOwner<'a> for StaticDef<'a> {}
3644impl<'a> ast::AttrsOwner<'a> for StaticDef<'a> {} 3651impl<'a> ast::AttrsOwner<'a> for StaticDef<'a> {}
@@ -3742,6 +3749,7 @@ impl<R: TreeRoot<RaTypes>> StructDefNode<R> {
3742} 3749}
3743 3750
3744 3751
3752impl<'a> ast::VisibilityOwner<'a> for StructDef<'a> {}
3745impl<'a> ast::NameOwner<'a> for StructDef<'a> {} 3753impl<'a> ast::NameOwner<'a> for StructDef<'a> {}
3746impl<'a> ast::TypeParamsOwner<'a> for StructDef<'a> {} 3754impl<'a> ast::TypeParamsOwner<'a> for StructDef<'a> {}
3747impl<'a> ast::AttrsOwner<'a> for StructDef<'a> {} 3755impl<'a> ast::AttrsOwner<'a> for StructDef<'a> {}
@@ -3902,6 +3910,7 @@ impl<R: TreeRoot<RaTypes>> TraitDefNode<R> {
3902} 3910}
3903 3911
3904 3912
3913impl<'a> ast::VisibilityOwner<'a> for TraitDef<'a> {}
3905impl<'a> ast::NameOwner<'a> for TraitDef<'a> {} 3914impl<'a> ast::NameOwner<'a> for TraitDef<'a> {}
3906impl<'a> ast::AttrsOwner<'a> for TraitDef<'a> {} 3915impl<'a> ast::AttrsOwner<'a> for TraitDef<'a> {}
3907impl<'a> ast::DocCommentsOwner<'a> for TraitDef<'a> {} 3916impl<'a> ast::DocCommentsOwner<'a> for TraitDef<'a> {}
@@ -4135,6 +4144,7 @@ impl<R: TreeRoot<RaTypes>> TypeDefNode<R> {
4135} 4144}
4136 4145
4137 4146
4147impl<'a> ast::VisibilityOwner<'a> for TypeDef<'a> {}
4138impl<'a> ast::NameOwner<'a> for TypeDef<'a> {} 4148impl<'a> ast::NameOwner<'a> for TypeDef<'a> {}
4139impl<'a> ast::TypeParamsOwner<'a> for TypeDef<'a> {} 4149impl<'a> ast::TypeParamsOwner<'a> for TypeDef<'a> {}
4140impl<'a> ast::AttrsOwner<'a> for TypeDef<'a> {} 4150impl<'a> ast::AttrsOwner<'a> for TypeDef<'a> {}
@@ -4409,6 +4419,43 @@ impl<'a> UseTreeList<'a> {
4409 } 4419 }
4410} 4420}
4411 4421
4422// Visibility
4423#[derive(Debug, Clone, Copy,)]
4424pub struct VisibilityNode<R: TreeRoot<RaTypes> = OwnedRoot> {
4425 pub(crate) syntax: SyntaxNode<R>,
4426}
4427pub type Visibility<'a> = VisibilityNode<RefRoot<'a>>;
4428
4429impl<R1: TreeRoot<RaTypes>, R2: TreeRoot<RaTypes>> PartialEq<VisibilityNode<R1>> for VisibilityNode<R2> {
4430 fn eq(&self, other: &VisibilityNode<R1>) -> bool { self.syntax == other.syntax }
4431}
4432impl<R: TreeRoot<RaTypes>> Eq for VisibilityNode<R> {}
4433impl<R: TreeRoot<RaTypes>> Hash for VisibilityNode<R> {
4434 fn hash<H: Hasher>(&self, state: &mut H) { self.syntax.hash(state) }
4435}
4436
4437impl<'a> AstNode<'a> for Visibility<'a> {
4438 fn cast(syntax: SyntaxNodeRef<'a>) -> Option<Self> {
4439 match syntax.kind() {
4440 VISIBILITY => Some(Visibility { syntax }),
4441 _ => None,
4442 }
4443 }
4444 fn syntax(self) -> SyntaxNodeRef<'a> { self.syntax }
4445}
4446
4447impl<R: TreeRoot<RaTypes>> VisibilityNode<R> {
4448 pub fn borrowed(&self) -> Visibility {
4449 VisibilityNode { syntax: self.syntax.borrowed() }
4450 }
4451 pub fn owned(&self) -> VisibilityNode {
4452 VisibilityNode { syntax: self.syntax.owned() }
4453 }
4454}
4455
4456
4457impl<'a> Visibility<'a> {}
4458
4412// WhereClause 4459// WhereClause
4413#[derive(Debug, Clone, Copy,)] 4460#[derive(Debug, Clone, Copy,)]
4414pub struct WhereClauseNode<R: TreeRoot<RaTypes> = OwnedRoot> { 4461pub struct WhereClauseNode<R: TreeRoot<RaTypes> = OwnedRoot> {
diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron
index aab4839a9..2abb9da61 100644
--- a/crates/ra_syntax/src/grammar.ron
+++ b/crates/ra_syntax/src/grammar.ron
@@ -247,6 +247,7 @@ Grammar(
247 ), 247 ),
248 "FnDef": ( 248 "FnDef": (
249 traits: [ 249 traits: [
250 "VisibilityOwner",
250 "NameOwner", 251 "NameOwner",
251 "TypeParamsOwner", 252 "TypeParamsOwner",
252 "AttrsOwner", 253 "AttrsOwner",
@@ -257,6 +258,7 @@ Grammar(
257 "RetType": (options: ["TypeRef"]), 258 "RetType": (options: ["TypeRef"]),
258 "StructDef": ( 259 "StructDef": (
259 traits: [ 260 traits: [
261 "VisibilityOwner",
260 "NameOwner", 262 "NameOwner",
261 "TypeParamsOwner", 263 "TypeParamsOwner",
262 "AttrsOwner", 264 "AttrsOwner",
@@ -264,10 +266,11 @@ Grammar(
264 ] 266 ]
265 ), 267 ),
266 "NamedFieldDefList": (collections: [["fields", "NamedFieldDef"]]), 268 "NamedFieldDefList": (collections: [["fields", "NamedFieldDef"]]),
267 "NamedFieldDef": ( traits: ["NameOwner", "AttrsOwner"], options: ["TypeRef"] ), 269 "NamedFieldDef": ( traits: ["VisibilityOwner", "NameOwner", "AttrsOwner"], options: ["TypeRef"] ),
268 "PosFieldList": (collections: [["fields", "PosField"]]), 270 "PosFieldList": (collections: [["fields", "PosField"]]),
269 "PosField": ( traits: ["AttrsOwner"], options: ["TypeRef"]), 271 "PosField": ( traits: ["VisibilityOwner", "AttrsOwner"], options: ["TypeRef"]),
270 "EnumDef": ( traits: [ 272 "EnumDef": ( traits: [
273 "VisibilityOwner",
271 "NameOwner", 274 "NameOwner",
272 "TypeParamsOwner", 275 "TypeParamsOwner",
273 "AttrsOwner", 276 "AttrsOwner",
@@ -275,27 +278,30 @@ Grammar(
275 ], options: [["variant_list", "EnumVariantList"]] ), 278 ], options: [["variant_list", "EnumVariantList"]] ),
276 "EnumVariantList": ( collections: [["variants", "EnumVariant"]] ), 279 "EnumVariantList": ( collections: [["variants", "EnumVariant"]] ),
277 "EnumVariant": ( traits: ["NameOwner"], options: ["Expr"] ), 280 "EnumVariant": ( traits: ["NameOwner"], options: ["Expr"] ),
278 "TraitDef": ( traits: ["NameOwner", "AttrsOwner", "DocCommentsOwner"] ), 281 "TraitDef": ( traits: ["VisibilityOwner", "NameOwner", "AttrsOwner", "DocCommentsOwner"] ),
279 "Module": ( 282 "Module": (
280 traits: ["NameOwner", "AttrsOwner", "DocCommentsOwner" ], 283 traits: ["VisibilityOwner", "NameOwner", "AttrsOwner", "DocCommentsOwner" ],
281 options: [ "ItemList" ] 284 options: [ "ItemList" ]
282 ), 285 ),
283 "ItemList": ( 286 "ItemList": (
284 traits: [ "FnDefOwner", "ModuleItemOwner" ], 287 traits: [ "FnDefOwner", "ModuleItemOwner" ],
285 ), 288 ),
286 "ConstDef": ( traits: [ 289 "ConstDef": ( traits: [
290 "VisibilityOwner",
287 "NameOwner", 291 "NameOwner",
288 "TypeParamsOwner", 292 "TypeParamsOwner",
289 "AttrsOwner", 293 "AttrsOwner",
290 "DocCommentsOwner" 294 "DocCommentsOwner"
291 ] ), 295 ] ),
292 "StaticDef": ( traits: [ 296 "StaticDef": ( traits: [
297 "VisibilityOwner",
293 "NameOwner", 298 "NameOwner",
294 "TypeParamsOwner", 299 "TypeParamsOwner",
295 "AttrsOwner", 300 "AttrsOwner",
296 "DocCommentsOwner" 301 "DocCommentsOwner"
297 ] ), 302 ] ),
298 "TypeDef": ( traits: [ 303 "TypeDef": ( traits: [
304 "VisibilityOwner",
299 "NameOwner", 305 "NameOwner",
300 "TypeParamsOwner", 306 "TypeParamsOwner",
301 "AttrsOwner", 307 "AttrsOwner",
@@ -482,6 +488,7 @@ Grammar(
482 ], 488 ],
483 ), 489 ),
484 490
491 "Visibility": (),
485 "Name": (), 492 "Name": (),
486 "NameRef": (), 493 "NameRef": (),
487 "MacroCall": ( options: [ "TokenTree", "Path" ] ), 494 "MacroCall": ( options: [ "TokenTree", "Path" ] ),