1 use crate::assist_context::{AssistContext, Assists};
2 use hir::{HasVisibility, HirDisplay, Module};
4 assists::{AssistId, AssistKind},
5 base_db::{FileId, Upcast},
6 defs::{Definition, NameRefClass},
9 ast::{self, edit::IndentLevel, NameRef},
10 AstNode, Direction, SyntaxKind, TextSize,
13 // Assist: generate_constant
15 // Generate a named constant.
18 // struct S { i: usize }
19 // impl S { pub fn new(n: usize) {} }
21 // let v = S::new(CAPA$0CITY);
26 // struct S { i: usize }
27 // impl S { pub fn new(n: usize) {} }
29 // const CAPACITY: usize = $0;
30 // let v = S::new(CAPACITY);
34 pub(crate) fn generate_constant(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
35 let constant_token = ctx.find_node_at_offset::<ast::NameRef>()?;
36 if constant_token.to_string().chars().any(|it| !(it.is_uppercase() || it == '_')) {
37 cov_mark::hit!(not_constant_name);
40 if NameRefClass::classify(&ctx.sema, &constant_token).is_some() {
41 cov_mark::hit!(already_defined);
44 let expr = constant_token.syntax().ancestors().find_map(ast::Expr::cast)?;
45 let statement = expr.syntax().ancestors().find_map(ast::Stmt::cast)?;
46 let ty = ctx.sema.type_of_expr(&expr)?;
47 let scope = ctx.sema.scope(statement.syntax())?;
48 let constant_module = scope.module();
49 let type_name = ty.original().display_source_code(ctx.db(), constant_module.into()).ok()?;
50 let target = statement.syntax().parent()?.text_range();
51 let path = constant_token.syntax().ancestors().find_map(ast::Path::cast)?;
53 let name_refs = path.segments().map(|s| s.name_ref());
54 let mut outer_exists = false;
55 let mut not_exist_name_ref = Vec::new();
56 let mut current_module = constant_module;
57 for name_ref in name_refs {
58 let name_ref_value = name_ref?;
59 let name_ref_class = NameRefClass::classify(&ctx.sema, &name_ref_value);
60 match name_ref_class {
61 Some(NameRefClass::Definition(Definition::Module(m))) => {
62 if !m.visibility(ctx.sema.db).is_visible_from(ctx.sema.db, constant_module.into()) {
72 not_exist_name_ref.push(name_ref_value);
76 let (offset, indent, file_id, post_string) =
77 target_data_for_generate_constant(ctx, current_module, constant_module).unwrap_or_else(
79 let indent = IndentLevel::from_node(statement.syntax());
80 (statement.syntax().text_range().start(), indent, None, format!("\n{}", indent))
84 let text = get_text_for_generate_constant(not_exist_name_ref, indent, outer_exists, type_name)?;
86 AssistId("generate_constant", AssistKind::QuickFix),
90 if let Some(file_id) = file_id {
91 builder.edit_file(file_id);
93 builder.insert(offset, format!("{}{}", text, post_string));
98 fn get_text_for_generate_constant(
99 mut not_exist_name_ref: Vec<NameRef>,
103 ) -> Option<String> {
104 let constant_token = not_exist_name_ref.pop()?;
105 let vis = if not_exist_name_ref.len() == 0 && !outer_exists { "" } else { "\npub " };
106 let mut text = format!("{}const {}: {} = $0;", vis, constant_token, type_name);
107 while let Some(name_ref) = not_exist_name_ref.pop() {
108 let vis = if not_exist_name_ref.len() == 0 && !outer_exists { "" } else { "\npub " };
109 text = text.replace("\n", "\n ");
110 text = format!("{}mod {} {{{}\n}}", vis, name_ref.to_string(), text);
112 Some(text.replace("\n", &format!("\n{}", indent)))
115 fn target_data_for_generate_constant(
116 ctx: &AssistContext<'_>,
117 current_module: Module,
118 constant_module: Module,
119 ) -> Option<(TextSize, IndentLevel, Option<FileId>, String)> {
120 if current_module == constant_module {
121 // insert in current file
124 let in_file_source = current_module.definition_source(ctx.sema.db);
125 let file_id = in_file_source.file_id.original_file(ctx.sema.db.upcast());
126 match in_file_source.value {
127 hir::ModuleSource::Module(module_node) => {
128 let indent = IndentLevel::from_node(module_node.syntax());
129 let l_curly_token = module_node.item_list()?.l_curly_token()?;
130 let offset = l_curly_token.text_range().end();
132 let siblings_has_newline = l_curly_token
133 .siblings_with_tokens(Direction::Next)
134 .find(|it| it.kind() == SyntaxKind::WHITESPACE && it.to_string().contains("\n"))
137 if siblings_has_newline { format!("{}", indent) } else { format!("\n{}", indent) };
138 Some((offset, indent + 1, Some(file_id), post_string))
140 _ => Some((TextSize::from(0), 0.into(), Some(file_id), "\n".into())),
147 use crate::tests::{check_assist, check_assist_not_applicable};
153 r#"struct S { i: usize }
155 pub fn new(n: usize) {}
158 let v = S::new(CAPA$0CITY);
160 r#"struct S { i: usize }
162 pub fn new(n: usize) {}
165 const CAPACITY: usize = $0;
166 let v = S::new(CAPACITY);
171 fn test_wont_apply_when_defined() {
172 cov_mark::check!(already_defined);
173 check_assist_not_applicable(
175 r#"struct S { i: usize }
177 pub fn new(n: usize) {}
180 const CAPACITY: usize = 10;
181 let v = S::new(CAPAC$0ITY);
186 fn test_wont_apply_when_maybe_not_constant() {
187 cov_mark::check!(not_constant_name);
188 check_assist_not_applicable(
190 r#"struct S { i: usize }
192 pub fn new(n: usize) {}
195 let v = S::new(capa$0city);
201 fn test_constant_with_path() {
209 pub const A_CONSTANT: i32 = $0;
218 fn test_constant_with_longer_path() {
225 foo::goo::A_CON$0STANT
229 pub const A_CONSTANT: i32 = $0;
239 fn test_constant_with_not_exist_longer_path() {
243 foo::goo::A_CON$0STANT
247 pub const A_CONSTANT: i32 = $0;