ide_completion/completions/
env_vars.rs

1//! Completes environment variables defined by Cargo
2//! (<https://doc.rust-lang.org/cargo/reference/environment-variables.html>)
3use 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        // We won't map into `option_env` as that generates `None` for non-existent env vars
62        // so fall back to this lookup
63        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}