diff options
Diffstat (limited to 'crates/ra_ide_api')
-rw-r--r-- | crates/ra_ide_api/src/expand_macro.rs | 112 | ||||
-rw-r--r-- | crates/ra_ide_api/src/lib.rs | 5 |
2 files changed, 117 insertions, 0 deletions
diff --git a/crates/ra_ide_api/src/expand_macro.rs b/crates/ra_ide_api/src/expand_macro.rs new file mode 100644 index 000000000..48dc90932 --- /dev/null +++ b/crates/ra_ide_api/src/expand_macro.rs | |||
@@ -0,0 +1,112 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use crate::{db::RootDatabase, FilePosition}; | ||
4 | use ra_db::SourceDatabase; | ||
5 | use rustc_hash::FxHashMap; | ||
6 | |||
7 | use hir::db::AstDatabase; | ||
8 | use ra_syntax::{ | ||
9 | algo::{find_node_at_offset, replace_descendants}, | ||
10 | ast::{self}, | ||
11 | AstNode, NodeOrToken, SyntaxKind, SyntaxNode, WalkEvent, | ||
12 | }; | ||
13 | |||
14 | fn insert_whitespaces(syn: SyntaxNode) -> String { | ||
15 | let mut res = String::new(); | ||
16 | |||
17 | let mut token_iter = syn | ||
18 | .preorder_with_tokens() | ||
19 | .filter_map(|event| { | ||
20 | if let WalkEvent::Enter(NodeOrToken::Token(token)) = event { | ||
21 | Some(token) | ||
22 | } else { | ||
23 | None | ||
24 | } | ||
25 | }) | ||
26 | .peekable(); | ||
27 | |||
28 | while let Some(token) = token_iter.next() { | ||
29 | res += &token.text().to_string(); | ||
30 | if token.kind().is_keyword() | ||
31 | || token.kind().is_literal() | ||
32 | || token.kind() == SyntaxKind::IDENT | ||
33 | { | ||
34 | if !token_iter.peek().map(|it| it.kind().is_punct()).unwrap_or(false) { | ||
35 | res += " "; | ||
36 | } | ||
37 | } | ||
38 | } | ||
39 | |||
40 | res | ||
41 | } | ||
42 | |||
43 | fn expand_macro_recur( | ||
44 | db: &RootDatabase, | ||
45 | source: hir::Source<&SyntaxNode>, | ||
46 | macro_call: &ast::MacroCall, | ||
47 | ) -> Option<SyntaxNode> { | ||
48 | let analyzer = hir::SourceAnalyzer::new(db, source, None); | ||
49 | let expansion = analyzer.expand(db, ¯o_call)?; | ||
50 | let expanded: SyntaxNode = db.parse_or_expand(expansion.file_id())?; | ||
51 | |||
52 | let children = expanded.descendants().filter_map(ast::MacroCall::cast); | ||
53 | let mut replaces = FxHashMap::default(); | ||
54 | |||
55 | for child in children.into_iter() { | ||
56 | let source = hir::Source::new(expansion.file_id(), source.ast); | ||
57 | let new_node = expand_macro_recur(db, source, &child)?; | ||
58 | |||
59 | replaces.insert(child.syntax().clone().into(), new_node.into()); | ||
60 | } | ||
61 | |||
62 | Some(replace_descendants(&expanded, &replaces)) | ||
63 | } | ||
64 | |||
65 | pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<(String, String)> { | ||
66 | let parse = db.parse(position.file_id); | ||
67 | let file = parse.tree(); | ||
68 | let name_ref = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset)?; | ||
69 | let mac = name_ref.syntax().ancestors().find_map(ast::MacroCall::cast)?; | ||
70 | |||
71 | let source = hir::Source::new(position.file_id.into(), mac.syntax()); | ||
72 | |||
73 | let expanded = expand_macro_recur(db, source, &mac)?; | ||
74 | |||
75 | // FIXME: | ||
76 | // macro expansion may lose all white space information | ||
77 | // But we hope someday we can use ra_fmt for that | ||
78 | let res = insert_whitespaces(expanded); | ||
79 | Some((name_ref.text().to_string(), res)) | ||
80 | } | ||
81 | |||
82 | #[cfg(test)] | ||
83 | mod tests { | ||
84 | use crate::mock_analysis::analysis_and_position; | ||
85 | |||
86 | fn check_expand_macro(fixture: &str, expected: (&str, &str)) { | ||
87 | let (analysis, pos) = analysis_and_position(fixture); | ||
88 | |||
89 | let result = analysis.expand_macro(pos).unwrap().unwrap(); | ||
90 | assert_eq!(result, (expected.0.to_string(), expected.1.to_string())); | ||
91 | } | ||
92 | |||
93 | #[test] | ||
94 | fn macro_expand_recursive_expansion() { | ||
95 | check_expand_macro( | ||
96 | r#" | ||
97 | //- /lib.rs | ||
98 | macro_rules! bar { | ||
99 | () => { fn b() {} } | ||
100 | } | ||
101 | macro_rules! foo { | ||
102 | () => { bar!(); } | ||
103 | } | ||
104 | macro_rules! baz { | ||
105 | () => { foo!(); } | ||
106 | } | ||
107 | f<|>oo!(); | ||
108 | "#, | ||
109 | ("foo", "fn b(){}"), | ||
110 | ); | ||
111 | } | ||
112 | } | ||
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index 110ddcd62..d1b73ef6f 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs | |||
@@ -42,6 +42,7 @@ mod display; | |||
42 | mod inlay_hints; | 42 | mod inlay_hints; |
43 | mod wasm_shims; | 43 | mod wasm_shims; |
44 | mod expand; | 44 | mod expand; |
45 | mod expand_macro; | ||
45 | 46 | ||
46 | #[cfg(test)] | 47 | #[cfg(test)] |
47 | mod marks; | 48 | mod marks; |
@@ -296,6 +297,10 @@ impl Analysis { | |||
296 | self.with_db(|db| syntax_tree::syntax_tree(&db, file_id, text_range)) | 297 | self.with_db(|db| syntax_tree::syntax_tree(&db, file_id, text_range)) |
297 | } | 298 | } |
298 | 299 | ||
300 | pub fn expand_macro(&self, position: FilePosition) -> Cancelable<Option<(String, String)>> { | ||
301 | self.with_db(|db| expand_macro::expand_macro(db, position)) | ||
302 | } | ||
303 | |||
299 | /// Returns an edit to remove all newlines in the range, cleaning up minor | 304 | /// Returns an edit to remove all newlines in the range, cleaning up minor |
300 | /// stuff like trailing commas. | 305 | /// stuff like trailing commas. |
301 | pub fn join_lines(&self, frange: FileRange) -> Cancelable<SourceChange> { | 306 | pub fn join_lines(&self, frange: FileRange) -> Cancelable<SourceChange> { |