Skip to main content

ide_diagnostics/handlers/
inactive_code.rs

1use cfg::DnfExpr;
2use stdx::format_to;
3
4use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, Severity};
5
6// Diagnostic: inactive-code
7//
8// This diagnostic is shown for code with inactive `#[cfg]` attributes.
9pub(crate) fn inactive_code(
10    ctx: &DiagnosticsContext<'_, '_>,
11    d: &hir::InactiveCode,
12) -> Option<Diagnostic> {
13    // If there's inactive code somewhere in a macro, don't propagate to the call-site.
14    if d.node.file_id.is_macro() {
15        return None;
16    }
17
18    let inactive = DnfExpr::new(&d.cfg).why_inactive(&d.opts);
19    let mut message = "code is inactive due to #[cfg] directives".to_owned();
20
21    if let Some(inactive) = inactive {
22        let inactive_reasons = inactive.to_string();
23
24        if inactive_reasons.is_empty() {
25            format_to!(message);
26        } else {
27            format_to!(message, ": {}", inactive);
28        }
29    }
30    // FIXME: This shouldn't be a diagnostic
31    let res = Diagnostic::new(
32        DiagnosticCode::Ra("inactive-code", Severity::WeakWarning),
33        message,
34        ctx.sema.diagnostics_display_range(d.node),
35    )
36    .stable()
37    .with_unused(true);
38    Some(res)
39}
40
41#[cfg(test)]
42mod tests {
43    use ide_db::RootDatabase;
44    use test_fixture::WithFixture;
45
46    use crate::{DiagnosticCode, DiagnosticsConfig, tests::check_diagnostics_with_config};
47
48    #[track_caller]
49    pub(crate) fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
50        let config = DiagnosticsConfig {
51            disabled: std::iter::once("unlinked-file".to_owned()).collect(),
52            ..DiagnosticsConfig::test_sample()
53        };
54        check_diagnostics_with_config(config, ra_fixture)
55    }
56
57    #[test]
58    fn cfg_diagnostics() {
59        check(
60            r#"
61fn f() {
62    // The three g̶e̶n̶d̶e̶r̶s̶ statements:
63
64    #[cfg(a)] fn f() {}  // Item statement
65  //^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
66    #[cfg(a)] {}         // Expression statement
67  //^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
68    #[cfg(a)] let x = 0; // let statement
69  //^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
70
71    fn abc() {}
72    abc(#[cfg(a)] 0);
73      //^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
74    let x = Struct {
75        #[cfg(a)] f: 0,
76      //^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
77    };
78    match () {
79        () => (),
80        #[cfg(a)] () => (),
81      //^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
82    }
83
84    #[cfg(a)] 0          // Trailing expression of block
85  //^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
86}
87        "#,
88        );
89    }
90
91    #[test]
92    fn inactive_item() {
93        // Additional tests in `cfg` crate. This only tests disabled cfgs.
94
95        check(
96            r#"
97    #[cfg(no)] pub fn f() {}
98  //^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: no is disabled
99
100    #[cfg(no)] #[cfg(no2)] mod m;
101  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: no is disabled
102
103    #[cfg(all(not(a), b))] enum E {}
104  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: b is disabled
105
106    #[cfg(feature = "std")] use std;
107  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: feature = "std" is disabled
108
109    #[cfg(any())] pub fn f() {}
110  //^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives
111"#,
112        );
113    }
114
115    #[test]
116    fn inactive_assoc_item() {
117        check(
118            r#"
119struct Foo;
120impl Foo {
121    #[cfg(any())] pub fn f() {}
122  //^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives
123}
124
125trait Bar {
126    #[cfg(any())] pub fn f() {}
127  //^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives
128}
129"#,
130        );
131    }
132
133    /// Tests that `cfg` attributes behind `cfg_attr` is handled properly.
134    #[test]
135    fn inactive_via_cfg_attr() {
136        check(
137            r#"
138    #[cfg_attr(not(never), cfg(no))] fn f() {}
139  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: no is disabled
140
141    #[cfg_attr(not(never), cfg(not(no)))] fn f() {}
142
143    #[cfg_attr(never, cfg(no))] fn g() {}
144
145    #[cfg_attr(not(never), inline, cfg(no))] fn h() {}
146  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: no is disabled
147"#,
148        );
149    }
150
151    #[test]
152    fn inactive_fields_and_variants() {
153        check(
154            r#"
155enum Foo {
156  #[cfg(a)] Bar,
157//^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
158  Baz {
159    #[cfg(a)] baz: String,
160  //^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
161  },
162  Qux(#[cfg(a)] String),
163    //^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
164}
165
166struct Baz {
167  #[cfg(a)] baz: String,
168//^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
169}
170
171struct Qux(#[cfg(a)] String);
172         //^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
173
174union FooBar {
175  #[cfg(a)] baz: u32,
176//^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
177}
178"#,
179        );
180    }
181
182    #[test]
183    fn modules() {
184        check(
185            r#"
186//- /main.rs
187  #[cfg(outline)] mod outline;
188//^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: outline is disabled
189
190  mod outline_inner;
191//^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: outline_inner is disabled
192
193  #[cfg(inline)] mod inline {}
194//^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: inline is disabled
195
196//- /outline_inner.rs
197#![cfg(outline_inner)]
198//- /outline.rs
199"#,
200        );
201    }
202
203    #[test]
204    fn cfg_true_false() {
205        check(
206            r#"
207  #[cfg(false)] fn inactive() {}
208//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: false is disabled
209
210  #[cfg(true)] fn active() {}
211
212  #[cfg(any(not(true), false))] fn inactive2() {}
213//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: true is enabled and false is disabled
214
215"#,
216        );
217    }
218
219    #[test]
220    fn inactive_crate() {
221        let db = RootDatabase::with_files(
222            r#"
223#![cfg(false)]
224
225fn foo() {}
226        "#,
227        );
228        let file_id = db.test_crate().root_file_id(&db);
229        let diagnostics = hir::attach_db(&db, || {
230            crate::full_diagnostics(
231                &db,
232                &DiagnosticsConfig::test_sample(),
233                &ide_db::assists::AssistResolveStrategy::All,
234                file_id.file_id(&db),
235            )
236        });
237        let [inactive_code] = &*diagnostics else {
238            panic!("expected one inactive_code diagnostic, found {diagnostics:#?}");
239        };
240        assert_eq!(
241            inactive_code.code,
242            DiagnosticCode::Ra("inactive-code", ide_db::Severity::WeakWarning)
243        );
244        assert_eq!(
245            inactive_code.message,
246            "code is inactive due to #[cfg] directives: false is disabled",
247        );
248        assert!(inactive_code.fixes.is_none());
249        let full_file_range = file_id.parse(&db).syntax_node().text_range();
250        assert_eq!(
251            inactive_code.range,
252            ide_db::FileRange { file_id: file_id.file_id(&db), range: full_file_range },
253        );
254    }
255}