ide_completion/completions/
env_vars.rs1use ide_db::syntax_helpers::node_ext::macro_call_for_string_token;
4use syntax::{
5 AstToken,
6 ast::{self, IsString},
7};
8
9use crate::{
10 CompletionItem, CompletionItemKind, completions::Completions, context::CompletionContext,
11};
12
13const CARGO_DEFINED_VARS: &[(&str, &str)] = &[
14 ("CARGO", "Path to the cargo binary performing the build"),
15 ("CARGO_MANIFEST_DIR", "The directory containing the manifest of your package"),
16 ("CARGO_MANIFEST_PATH", "The path to the manifest of your package"),
17 ("CARGO_PKG_VERSION", "The full version of your package"),
18 ("CARGO_PKG_VERSION_MAJOR", "The major version of your package"),
19 ("CARGO_PKG_VERSION_MINOR", "The minor version of your package"),
20 ("CARGO_PKG_VERSION_PATCH", "The patch version of your package"),
21 ("CARGO_PKG_VERSION_PRE", "The pre-release version of your package"),
22 ("CARGO_PKG_AUTHORS", "Colon separated list of authors from the manifest of your package"),
23 ("CARGO_PKG_NAME", "The name of your package"),
24 ("CARGO_PKG_DESCRIPTION", "The description from the manifest of your package"),
25 ("CARGO_PKG_HOMEPAGE", "The home page from the manifest of your package"),
26 ("CARGO_PKG_REPOSITORY", "The repository from the manifest of your package"),
27 ("CARGO_PKG_LICENSE", "The license from the manifest of your package"),
28 ("CARGO_PKG_LICENSE_FILE", "The license file from the manifest of your package"),
29 (
30 "CARGO_PKG_RUST_VERSION",
31 "The Rust version from the manifest of your package. Note that this is the minimum Rust version supported by the package, not the current Rust version",
32 ),
33 ("CARGO_CRATE_NAME", "The name of the crate that is currently being compiled"),
34 (
35 "CARGO_BIN_NAME",
36 "The name of the binary that is currently being compiled (if it is a binary). This name does not include any file extension, such as .exe",
37 ),
38 (
39 "CARGO_PRIMARY_PACKAGE",
40 "This environment variable will be set if the package being built is primary. Primary packages are the ones the user selected on the command-line, either with -p flags or the defaults based on the current directory and the default workspace members. This environment variable will not be set when building dependencies. This is only set when compiling the package (not when running binaries or tests)",
41 ),
42 (
43 "CARGO_TARGET_TMPDIR",
44 "Only set when building integration test or benchmark code. This is a path to a directory inside the target directory where integration tests or benchmarks are free to put any data needed by the tests/benches. Cargo initially creates this directory but doesn't manage its content in any way, this is the responsibility of the test code",
45 ),
46];
47
48pub(crate) fn complete_cargo_env_vars(
49 acc: &mut Completions,
50 ctx: &CompletionContext<'_>,
51 original: &ast::String,
52 expanded: &ast::String,
53) -> Option<()> {
54 let descends = ctx.sema.descend_into_macros_exact_with_file(original.syntax().clone());
55 let macro_file = descends.first()?.file_id.macro_file();
56
57 let is_in_env_expansion = macro_file.is_some_and(|it| it.is_env_or_option_env(ctx.sema.db));
58 if !is_in_env_expansion {
59 let call = macro_call_for_string_token(expanded)?;
60 let makro = ctx.sema.resolve_macro_call(&call)?;
61 if !makro.is_env_or_option_env(ctx.sema.db) {
64 return None;
65 }
66 }
67 let range = original.text_range_between_quotes()?;
68
69 CARGO_DEFINED_VARS.iter().for_each(|&(var, detail)| {
70 let mut item = CompletionItem::new(CompletionItemKind::Keyword, range, var, ctx.edition);
71 item.detail(detail);
72 item.add_to(acc, ctx.db);
73 });
74
75 Some(())
76}
77
78#[cfg(test)]
79mod tests {
80 use crate::tests::{check_edit, completion_list};
81
82 #[test]
83 fn completes_env_variable_in_env() {
84 check_edit(
85 "CARGO_BIN_NAME",
86 r#"
87//- minicore: env
88fn main() {
89 let foo = env!("CAR$0");
90}
91 "#,
92 r#"
93fn main() {
94 let foo = env!("CARGO_BIN_NAME");
95}
96 "#,
97 );
98 }
99
100 #[test]
101 fn completes_env_variable_in_option_env() {
102 check_edit(
103 "CARGO_BIN_NAME",
104 r#"
105//- minicore: env
106fn main() {
107 let foo = option_env!("CAR$0");
108}
109 "#,
110 r#"
111fn main() {
112 let foo = option_env!("CARGO_BIN_NAME");
113}
114 "#,
115 );
116 }
117
118 #[test]
119 fn complete_in_expanded_env_macro() {
120 check_edit(
121 "CARGO_BIN_NAME",
122 r#"
123//- minicore: env
124macro_rules! bar {
125 ($($arg:tt)*) => { $($arg)* }
126}
127
128fn main() {
129 let foo = bar!(env!("CA$0"));
130}
131 "#,
132 r#"
133macro_rules! bar {
134 ($($arg:tt)*) => { $($arg)* }
135}
136
137fn main() {
138 let foo = bar!(env!("CARGO_BIN_NAME"));
139}
140 "#,
141 );
142
143 check_edit(
144 "CARGO_BIN_NAME",
145 r#"
146//- minicore: env, fmt
147fn main() {
148 let foo = format_args!("{}", env!("CA$0"));
149}
150 "#,
151 r#"
152fn main() {
153 let foo = format_args!("{}", env!("CARGO_BIN_NAME"));
154}
155 "#,
156 );
157 }
158
159 #[test]
160 fn doesnt_complete_in_random_strings() {
161 let fixture = r#"
162 fn main() {
163 let foo = "CA$0";
164 }
165 "#;
166
167 let completions = completion_list(fixture);
168 assert!(completions.is_empty(), "Completions weren't empty: {completions}");
169 }
170
171 #[test]
172 fn doesnt_complete_in_random_macro() {
173 let fixture = r#"
174 macro_rules! bar {
175 ($($arg:tt)*) => { 0 }
176 }
177
178 fn main() {
179 let foo = bar!("CA$0");
180
181 }
182 "#;
183
184 let completions = completion_list(fixture);
185 assert!(completions.is_empty(), "Completions weren't empty: {completions}");
186 }
187
188 #[test]
189 fn doesnt_complete_for_shadowed_macro() {
190 let fixture = r#"
191 macro_rules! env {
192 ($var:literal) => { 0 }
193 }
194
195 fn main() {
196 let foo = env!("CA$0");
197 }
198 "#;
199
200 let completions = completion_list(fixture);
201 assert!(completions.is_empty(), "Completions weren't empty: {completions}")
202 }
203}