Skip to content

Commit 6376671

Browse files
committed
fix: “Generate constant” ignores the path prefix of the identifier
1 parent 1ff5b2c commit 6376671

File tree

1 file changed

+149
-15
lines changed

1 file changed

+149
-15
lines changed

crates/ide-assists/src/handlers/generate_constant.rs

+149-15
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use crate::assist_context::{AssistContext, Assists};
2-
use hir::HirDisplay;
2+
use hir::{HasVisibility, HirDisplay, Module};
33
use ide_db::{
44
assists::{AssistId, AssistKind},
5-
defs::NameRefClass,
5+
base_db::{FileId, Upcast},
6+
defs::{Definition, NameRefClass},
67
};
78
use syntax::{
8-
ast::{self, edit::IndentLevel},
9-
AstNode,
9+
ast::{self, edit::IndentLevel, NameRef},
10+
AstNode, Direction, SyntaxKind, TextSize,
1011
};
1112

1213
// Assist: generate_constant
@@ -32,13 +33,6 @@ use syntax::{
3233

3334
pub(crate) fn generate_constant(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
3435
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());
4236
if constant_token.to_string().chars().any(|it| !(it.is_uppercase() || it == '_')) {
4337
cov_mark::hit!(not_constant_name);
4438
return None;
@@ -47,20 +41,104 @@ pub(crate) fn generate_constant(acc: &mut Assists, ctx: &AssistContext) -> Optio
4741
cov_mark::hit!(already_defined);
4842
return None;
4943
}
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()?;
5050
let target = statement.syntax().parent()?.text_range();
51+
let path = constant_token.syntax().ancestors().find_map(ast::Path::cast)?;
52+
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()) {
63+
return None;
64+
}
65+
outer_exists = true;
66+
current_module = m;
67+
}
68+
Some(_) => {
69+
return None;
70+
}
71+
None => {
72+
not_exist_name_ref.push(name_ref_value);
73+
}
74+
}
75+
}
76+
let (offset, indent, file_id, post_string) =
77+
target_data_for_generate_constant(ctx, current_module, constant_module).unwrap_or_else(|| {
78+
let indent = IndentLevel::from_node(statement.syntax());
79+
(statement.syntax().text_range().start(), indent, None, format!("\n{}", indent))
80+
});
81+
82+
let text = get_text_for_generate_constant(not_exist_name_ref, indent, outer_exists, type_name)?;
5183
acc.add(
5284
AssistId("generate_constant", AssistKind::QuickFix),
5385
"Generate constant",
5486
target,
5587
|builder| {
56-
builder.insert(
57-
statement.syntax().text_range().start(),
58-
format!("const {}: {} = $0;\n{}", constant_token, type_name, indent),
59-
);
88+
if file_id.is_some() {
89+
builder.edit_file(file_id.unwrap());
90+
}
91+
builder.insert(offset, format!("{}{}", text, post_string));
6092
},
6193
)
6294
}
6395

96+
fn get_text_for_generate_constant(
97+
mut not_exist_name_ref: Vec<NameRef>,
98+
indent: IndentLevel,
99+
outer_exists: bool,
100+
type_name: String,
101+
) -> Option<String> {
102+
let constant_token = not_exist_name_ref.pop()?;
103+
let vis = if not_exist_name_ref.len() == 0 && !outer_exists { "" } else { "\npub " };
104+
let mut text = format!("{}const {}: {} = $0;", vis, constant_token, type_name);
105+
while let Some(name_ref) = not_exist_name_ref.pop() {
106+
let vis = if not_exist_name_ref.len() == 0 && !outer_exists { "" } else { "\npub " };
107+
text = text.replace("\n", "\n ");
108+
text = format!("{}mod {} {{{}\n}}", vis, name_ref.to_string(), text);
109+
}
110+
Some(text.replace("\n", &format!("\n{}", indent)))
111+
}
112+
113+
fn target_data_for_generate_constant(
114+
ctx: &AssistContext,
115+
current_module: Module,
116+
constant_module: Module,
117+
) -> Option<(TextSize, IndentLevel, Option<FileId>, String)> {
118+
if current_module == constant_module {
119+
// insert in current file
120+
return None;
121+
}
122+
let in_file_source = current_module.definition_source(ctx.sema.db);
123+
let file_id = in_file_source.file_id.original_file(ctx.sema.db.upcast());
124+
match in_file_source.value {
125+
hir::ModuleSource::Module(module_node) => {
126+
let indent = IndentLevel::from_node(module_node.syntax());
127+
let l_curly_token = module_node.item_list()?.l_curly_token()?;
128+
let offset = l_curly_token.text_range().end();
129+
130+
let siblings_has_newline = l_curly_token
131+
.siblings_with_tokens(Direction::Next)
132+
.find(|it| it.kind() == SyntaxKind::WHITESPACE && it.to_string().contains("\n"))
133+
.is_some();
134+
let post_string =
135+
if siblings_has_newline { format!("{}", indent) } else { format!("\n{}", indent) };
136+
Some((offset, indent + 1, Some(file_id), post_string))
137+
}
138+
_ => Some((TextSize::from(0), 0.into(), Some(file_id), "\n".into())),
139+
}
140+
}
141+
64142
#[cfg(test)]
65143
mod tests {
66144
use super::*;
@@ -113,6 +191,62 @@ impl S {
113191
}
114192
fn main() {
115193
let v = S::new(capa$0city);
194+
}"#,
195+
);
196+
}
197+
198+
#[test]
199+
fn test_constant_with_path() {
200+
check_assist(
201+
generate_constant,
202+
r#"mod foo {}
203+
fn bar() -> i32 {
204+
foo::A_CON$0STANT
205+
}"#,
206+
r#"mod foo {
207+
pub const A_CONSTANT: i32 = $0;
208+
}
209+
fn bar() -> i32 {
210+
foo::A_CONSTANT
211+
}"#,
212+
);
213+
}
214+
215+
#[test]
216+
fn test_constant_with_longer_path() {
217+
check_assist(
218+
generate_constant,
219+
r#"mod foo {
220+
pub mod goo {}
221+
}
222+
fn bar() -> i32 {
223+
foo::goo::A_CON$0STANT
224+
}"#,
225+
r#"mod foo {
226+
pub mod goo {
227+
pub const A_CONSTANT: i32 = $0;
228+
}
229+
}
230+
fn bar() -> i32 {
231+
foo::goo::A_CONSTANT
232+
}"#,
233+
);
234+
}
235+
236+
#[test]
237+
fn test_constant_with_not_exist_longer_path() {
238+
check_assist(
239+
generate_constant,
240+
r#"fn bar() -> i32 {
241+
foo::goo::A_CON$0STANT
242+
}"#,
243+
r#"mod foo {
244+
pub mod goo {
245+
pub const A_CONSTANT: i32 = $0;
246+
}
247+
}
248+
fn bar() -> i32 {
249+
foo::goo::A_CONSTANT
116250
}"#,
117251
);
118252
}

0 commit comments

Comments
 (0)