]> git.lizzy.rs Git - rust.git/blob - crates/ide-completion/src/completions/env_vars.rs
Restrict auto-completion for only built-in macros
[rust.git] / crates / ide-completion / src / completions / env_vars.rs
1 //! Completes environment variables defined by Cargo (https://doc.rust-lang.org/cargo/reference/environment-variables.html)
2 use hir::Semantics;
3 use ide_db::{syntax_helpers::node_ext::get_outer_macro, RootDatabase};
4 use syntax::ast::{self, IsString};
5
6 use crate::{context::CompletionContext, CompletionItem, CompletionItemKind};
7
8 use super::Completions;
9 const CARGO_DEFINED_VARS: &[(&str, &str)] = &[
10 ("CARGO","Path to the cargo binary performing the build"),
11 ("CARGO_MANIFEST_DIR","The directory containing the manifest of your package"),
12 ("CARGO_PKG_VERSION","The full version of your package"),
13 ("CARGO_PKG_VERSION_MAJOR","The major version of your package"),
14 ("CARGO_PKG_VERSION_MINOR","The minor version of your package"),
15 ("CARGO_PKG_VERSION_PATCH","The patch version of your package"),
16 ("CARGO_PKG_VERSION_PRE","The pre-release version of your package"),
17 ("CARGO_PKG_AUTHORS","Colon separated list of authors from the manifest of your package"),
18 ("CARGO_PKG_NAME","The name of your package"),
19 ("CARGO_PKG_DESCRIPTION","The description from the manifest of your package"),
20 ("CARGO_PKG_HOMEPAGE","The home page from the manifest of your package"),
21 ("CARGO_PKG_REPOSITORY","The repository from the manifest of your package"),
22 ("CARGO_PKG_LICENSE","The license from the manifest of your package"),
23 ("CARGO_PKG_LICENSE_FILE","The license file from the manifest of your package"),
24 ("CARGO_PKG_RUST_VERSION","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"),
25 ("CARGO_CRATE_NAME","The name of the crate that is currently being compiled"),
26 ("CARGO_BIN_NAME","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"),
27 ("CARGO_PRIMARY_PACKAGE","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)"),
28 ("CARGO_TARGET_TMPDIR","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")
29 ];
30
31 pub(crate) fn complete_cargo_env_vars(
32     acc: &mut Completions,
33     ctx: &CompletionContext<'_>,
34     expanded: &ast::String,
35 ) -> Option<()> {
36     guard_env_macro(expanded, &ctx.sema, &ctx.db)?;
37     let range = expanded.text_range_between_quotes()?;
38
39     CARGO_DEFINED_VARS.iter().for_each(|(var, detail)| {
40         let mut item = CompletionItem::new(CompletionItemKind::Keyword, range, var);
41         item.detail(*detail);
42         item.add_to(acc);
43     });
44
45     Some(())
46 }
47
48 fn guard_env_macro(
49     string: &ast::String,
50     semantics: &Semantics<'_, RootDatabase>,
51     db: &RootDatabase,
52 ) -> Option<()> {
53     let call = get_outer_macro(string)?;
54     let name = call.path()?.segment()?.name_ref()?;
55     let makro = semantics.resolve_macro_call(&call)?;
56
57     match name.text().as_str() {
58         "env" | "option_env" if makro.kind(db) == hir::MacroKind::BuiltIn => Some(()),
59         _ => None,
60     }
61 }
62
63 #[cfg(test)]
64 mod tests {
65     use crate::tests::{check_edit, completion_list};
66
67     fn check(macro_name: &str) {
68         check_edit(
69             "CARGO_BIN_NAME",
70             &format!(
71                 r#"
72             #[rustc_builtin_macro]
73             macro_rules! {} {{
74                 ($var:literal) => {{ 0 }}
75             }}
76
77             fn main() {{
78                 let foo = {}!("CAR$0");
79             }}
80         "#,
81                 macro_name, macro_name
82             ),
83             &format!(
84                 r#"
85             #[rustc_builtin_macro]
86             macro_rules! {} {{
87                 ($var:literal) => {{ 0 }}
88             }}
89
90             fn main() {{
91                 let foo = {}!("CARGO_BIN_NAME");
92             }}
93         "#,
94                 macro_name, macro_name
95             ),
96         );
97     }
98     #[test]
99     fn completes_env_variable_in_env() {
100         check("env")
101     }
102
103     #[test]
104     fn completes_env_variable_in_option_env() {
105         check("option_env");
106     }
107
108     #[test]
109     fn doesnt_complete_in_random_strings() {
110         let fixture = r#"
111             fn main() {
112                 let foo = "CA$0";
113             }
114         "#;
115
116         let completions = completion_list(fixture);
117         assert!(completions.is_empty(), "Completions weren't empty: {}", completions);
118     }
119
120     #[test]
121     fn doesnt_complete_in_random_macro() {
122         let fixture = r#"
123             macro_rules! bar {
124                 ($($arg:tt)*) => { 0 }
125             }
126
127             fn main() {
128                 let foo = bar!("CA$0");
129
130             }
131         "#;
132
133         let completions = completion_list(fixture);
134         assert!(completions.is_empty(), "Completions weren't empty: {}", completions);
135     }
136
137     #[test]
138     fn doesnt_complete_for_shadowed_macro() {
139         let fixture = r#"
140             macro_rules! env {
141                 ($var:literal) => { 0 }
142             }
143
144             fn main() {
145                 let foo = env!("CA$0");
146             }
147         "#;
148
149         let completions = completion_list(fixture);
150         assert!(completions.is_empty(), "Completions weren't empty: {}", completions)
151     }
152 }