ra_ap_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 is_in_env_expansion = ctx
55        .sema
56        .hir_file_for(&expanded.syntax().parent()?)
57        .macro_file()
58        .is_some_and(|it| it.is_env_or_option_env(ctx.sema.db));
59    if !is_in_env_expansion {
60        let call = macro_call_for_string_token(expanded)?;
61        let makro = ctx.sema.resolve_macro_call(&call)?;
62        // We won't map into `option_env` as that generates `None` for non-existent env vars
63        // so fall back to this lookup
64        if !makro.is_env_or_option_env(ctx.sema.db) {
65            return None;
66        }
67    }
68    let range = original.text_range_between_quotes()?;
69
70    CARGO_DEFINED_VARS.iter().for_each(|&(var, detail)| {
71        let mut item = CompletionItem::new(CompletionItemKind::Keyword, range, var, ctx.edition);
72        item.detail(detail);
73        item.add_to(acc, ctx.db);
74    });
75
76    Some(())
77}
78
79#[cfg(test)]
80mod tests {
81    use crate::tests::{check_edit, completion_list};
82
83    #[test]
84    fn completes_env_variable_in_env() {
85        check_edit(
86            "CARGO_BIN_NAME",
87            r#"
88//- minicore: env
89fn main() {
90    let foo = env!("CAR$0");
91}
92        "#,
93            r#"
94fn main() {
95    let foo = env!("CARGO_BIN_NAME");
96}
97        "#,
98        );
99    }
100
101    #[test]
102    fn completes_env_variable_in_option_env() {
103        check_edit(
104            "CARGO_BIN_NAME",
105            r#"
106//- minicore: env
107fn main() {
108    let foo = option_env!("CAR$0");
109}
110        "#,
111            r#"
112fn main() {
113    let foo = option_env!("CARGO_BIN_NAME");
114}
115        "#,
116        );
117    }
118
119    #[test]
120    fn doesnt_complete_in_random_strings() {
121        let fixture = r#"
122            fn main() {
123                let foo = "CA$0";
124            }
125        "#;
126
127        let completions = completion_list(fixture);
128        assert!(completions.is_empty(), "Completions weren't empty: {completions}");
129    }
130
131    #[test]
132    fn doesnt_complete_in_random_macro() {
133        let fixture = r#"
134            macro_rules! bar {
135                ($($arg:tt)*) => { 0 }
136            }
137
138            fn main() {
139                let foo = bar!("CA$0");
140
141            }
142        "#;
143
144        let completions = completion_list(fixture);
145        assert!(completions.is_empty(), "Completions weren't empty: {completions}");
146    }
147
148    #[test]
149    fn doesnt_complete_for_shadowed_macro() {
150        let fixture = r#"
151            macro_rules! env {
152                ($var:literal) => { 0 }
153            }
154
155            fn main() {
156                let foo = env!("CA$0");
157            }
158        "#;
159
160        let completions = completion_list(fixture);
161        assert!(completions.is_empty(), "Completions weren't empty: {completions}")
162    }
163}