]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/generate_constant.rs
Merge #11481
[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 constant_token = ctx.find_node_at_offset::<ast::NameRef>()?;
35     let expr = constant_token.syntax().ancestors().find_map(ast::Expr::cast)?;
36     let statement = expr.syntax().ancestors().find_map(ast::Stmt::cast)?;
37     let ty = ctx.sema.type_of_expr(&expr)?;
38     let scope = ctx.sema.scope(statement.syntax());
39     let module = scope.module()?;
40     let type_name = ty.original().display_source_code(ctx.db(), module.into()).ok()?;
41     let indent = IndentLevel::from_node(statement.syntax());
42     if constant_token.to_string().chars().any(|it| !(it.is_uppercase() || it == '_')) {
43         cov_mark::hit!(not_constant_name);
44         return None;
45     }
46     if NameRefClass::classify(&ctx.sema, &constant_token).is_some() {
47         cov_mark::hit!(already_defined);
48         return None;
49     }
50     let target = statement.syntax().parent()?.text_range();
51     acc.add(
52         AssistId("generate_constant", AssistKind::QuickFix),
53         "Generate constant",
54         target,
55         |builder| {
56             builder.insert(
57                 statement.syntax().text_range().start(),
58                 format!("const {}: {} = $0;\n{}", constant_token, type_name, indent),
59             );
60         },
61     )
62 }
63
64 #[cfg(test)]
65 mod tests {
66     use super::*;
67     use crate::tests::{check_assist, check_assist_not_applicable};
68
69     #[test]
70     fn test_trivial() {
71         check_assist(
72             generate_constant,
73             r#"struct S { i: usize }
74 impl S {
75     pub fn new(n: usize) {}
76 }
77 fn main() {
78     let v = S::new(CAPA$0CITY);
79 }"#,
80             r#"struct S { i: usize }
81 impl S {
82     pub fn new(n: usize) {}
83 }
84 fn main() {
85     const CAPACITY: usize = $0;
86     let v = S::new(CAPACITY);
87 }"#,
88         );
89     }
90     #[test]
91     fn test_wont_apply_when_defined() {
92         cov_mark::check!(already_defined);
93         check_assist_not_applicable(
94             generate_constant,
95             r#"struct S { i: usize }
96 impl S {
97     pub fn new(n: usize) {}
98 }
99 fn main() {
100     const CAPACITY: usize = 10;
101     let v = S::new(CAPAC$0ITY);
102 }"#,
103         );
104     }
105     #[test]
106     fn test_wont_apply_when_maybe_not_constant() {
107         cov_mark::check!(not_constant_name);
108         check_assist_not_applicable(
109             generate_constant,
110             r#"struct S { i: usize }
111 impl S {
112     pub fn new(n: usize) {}
113 }
114 fn main() {
115     let v = S::new(capa$0city);
116 }"#,
117         );
118     }
119 }