]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/generate_constant.rs
Add generate_constant assist
[rust.git] / crates / ide_assists / src / handlers / generate_constant.rs
1 use crate::assist_context::{AssistContext, Assists};
2 use hir::HirDisplay;
3 use ide_db::{
4     assists::{AssistId, AssistKind},
5     defs::NameRefClass,
6 };
7 use syntax::{
8     ast::{self, edit::IndentLevel},
9     AstNode,
10 };
11
12 // Assist: generate_constant
13 //
14 // Generate a named constant.
15 //
16 // ```
17 // struct S { i: usize }
18 // impl S { pub fn new(n: usize) {} }
19 // fn main() {
20 //     let v = S::new(CAPA$0CITY);
21 // }
22 // ```
23 // ->
24 // ```
25 // struct S { i: usize }
26 // impl S { pub fn new(n: usize) {} }
27 // fn main() {
28 //     const CAPACITY: usize = $0;
29 //     let v = S::new(CAPACITY);
30 // }
31 // ```
32
33 pub(crate) fn generate_constant(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
34     let statement = ctx.find_node_at_offset::<ast::Stmt>()?;
35     let arg_list = ctx.find_node_at_offset::<ast::ArgList>()?;
36     let expr = ctx.find_node_at_offset::<ast::Expr>()?;
37     let constant_token = ctx.find_node_at_offset::<ast::NameRef>()?;
38     let ty = ctx.sema.type_of_expr(&expr)?;
39     let scope = ctx.sema.scope(statement.syntax());
40     let module = scope.module()?;
41     let type_name = ty.original().display_source_code(ctx.db(), module.into()).ok()?;
42     let indent = IndentLevel::from_node(statement.syntax());
43     if !arg_list.syntax().text_range().contains_range(constant_token.syntax().text_range()) {
44         return None;
45     }
46     if constant_token.to_string().chars().any(|it| !(it.is_uppercase() || it == '_')) {
47         cov_mark::hit!(not_constant_name);
48         return None;
49     }
50     if NameRefClass::classify(&ctx.sema, &constant_token).is_some() {
51         cov_mark::hit!(already_defined);
52         return None;
53     }
54     let target = statement.syntax().parent()?.text_range();
55     let statement_syntax = statement.syntax().clone_for_update();
56     acc.add(
57         AssistId("generate_constant", AssistKind::QuickFix),
58         "Generate constant",
59         target,
60         |builder| {
61             builder.replace(
62                 statement.syntax().text_range(),
63                 format!(
64                     "const {}: {} = $0;\n{}{}",
65                     constant_token,
66                     type_name,
67                     indent,
68                     statement_syntax.text()
69                 ),
70             );
71         },
72     )
73 }
74
75 #[cfg(test)]
76 mod tests {
77     use super::*;
78     use crate::tests::{check_assist, check_assist_not_applicable};
79
80     #[test]
81     fn test_trivial() {
82         check_assist(
83             generate_constant,
84             r#"struct S { i: usize }
85 impl S {
86     pub fn new(n: usize) {}
87 }
88 fn main() {
89     let v = S::new(CAPA$0CITY);
90 }"#,
91             r#"struct S { i: usize }
92 impl S {
93     pub fn new(n: usize) {}
94 }
95 fn main() {
96     const CAPACITY: usize = $0;
97     let v = S::new(CAPACITY);
98 }"#,
99         );
100     }
101     #[test]
102     fn test_wont_apply_when_defined() {
103         cov_mark::check!(already_defined);
104         check_assist_not_applicable(
105             generate_constant,
106             r#"struct S { i: usize }
107 impl S {
108     pub fn new(n: usize) {}
109 }
110 fn main() {
111     const CAPACITY: usize = 10;
112     let v = S::new(CAPAC$0ITY);
113 }"#,
114         );
115     }
116     #[test]
117     fn test_wont_apply_when_maybe_not_constant() {
118         cov_mark::check!(not_constant_name);
119         check_assist_not_applicable(
120             generate_constant,
121             r#"struct S { i: usize }
122 impl S {
123     pub fn new(n: usize) {}
124 }
125 fn main() {
126     let v = S::new(capa$0city);
127 }"#,
128         );
129     }
130 }