• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #[cfg(not(feature = "in-rust-tree"))]
2 mod sourcegen;
3 
4 use expect_test::Expect;
5 use ide_db::{
6     assists::AssistResolveStrategy,
7     base_db::{fixture::WithFixture, SourceDatabaseExt},
8     RootDatabase,
9 };
10 use stdx::trim_indent;
11 use test_utils::{assert_eq_text, extract_annotations, MiniCore};
12 
13 use crate::{DiagnosticsConfig, ExprFillDefaultMode, Severity};
14 
15 /// Takes a multi-file input fixture with annotated cursor positions,
16 /// and checks that:
17 ///  * a diagnostic is produced
18 ///  * the first diagnostic fix trigger range touches the input cursor position
19 ///  * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied
20 #[track_caller]
check_fix(ra_fixture_before: &str, ra_fixture_after: &str)21 pub(crate) fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) {
22     check_nth_fix(0, ra_fixture_before, ra_fixture_after);
23 }
24 /// Takes a multi-file input fixture with annotated cursor positions,
25 /// and checks that:
26 ///  * a diagnostic is produced
27 ///  * every diagnostic fixes trigger range touches the input cursor position
28 ///  * that the contents of the file containing the cursor match `after` after each diagnostic fix is applied
check_fixes(ra_fixture_before: &str, ra_fixtures_after: Vec<&str>)29 pub(crate) fn check_fixes(ra_fixture_before: &str, ra_fixtures_after: Vec<&str>) {
30     for (i, ra_fixture_after) in ra_fixtures_after.iter().enumerate() {
31         check_nth_fix(i, ra_fixture_before, ra_fixture_after)
32     }
33 }
34 
35 #[track_caller]
check_nth_fix(nth: usize, ra_fixture_before: &str, ra_fixture_after: &str)36 fn check_nth_fix(nth: usize, ra_fixture_before: &str, ra_fixture_after: &str) {
37     let after = trim_indent(ra_fixture_after);
38 
39     let (db, file_position) = RootDatabase::with_position(ra_fixture_before);
40     let mut conf = DiagnosticsConfig::test_sample();
41     conf.expr_fill_default = ExprFillDefaultMode::Default;
42     let diagnostic =
43         super::diagnostics(&db, &conf, &AssistResolveStrategy::All, file_position.file_id)
44             .pop()
45             .expect("no diagnostics");
46     let fix = &diagnostic.fixes.expect("diagnostic misses fixes")[nth];
47     let actual = {
48         let source_change = fix.source_change.as_ref().unwrap();
49         let file_id = *source_change.source_file_edits.keys().next().unwrap();
50         let mut actual = db.file_text(file_id).to_string();
51 
52         for edit in source_change.source_file_edits.values() {
53             edit.apply(&mut actual);
54         }
55         actual
56     };
57 
58     assert!(
59         fix.target.contains_inclusive(file_position.offset),
60         "diagnostic fix range {:?} does not touch cursor position {:?}",
61         fix.target,
62         file_position.offset
63     );
64     assert_eq_text!(&after, &actual);
65 }
66 
67 /// Checks that there's a diagnostic *without* fix at `$0`.
check_no_fix(ra_fixture: &str)68 pub(crate) fn check_no_fix(ra_fixture: &str) {
69     let (db, file_position) = RootDatabase::with_position(ra_fixture);
70     let diagnostic = super::diagnostics(
71         &db,
72         &DiagnosticsConfig::test_sample(),
73         &AssistResolveStrategy::All,
74         file_position.file_id,
75     )
76     .pop()
77     .unwrap();
78     assert!(diagnostic.fixes.is_none(), "got a fix when none was expected: {diagnostic:?}");
79 }
80 
check_expect(ra_fixture: &str, expect: Expect)81 pub(crate) fn check_expect(ra_fixture: &str, expect: Expect) {
82     let (db, file_id) = RootDatabase::with_single_file(ra_fixture);
83     let diagnostics = super::diagnostics(
84         &db,
85         &DiagnosticsConfig::test_sample(),
86         &AssistResolveStrategy::All,
87         file_id,
88     );
89     expect.assert_debug_eq(&diagnostics)
90 }
91 
92 #[track_caller]
check_diagnostics(ra_fixture: &str)93 pub(crate) fn check_diagnostics(ra_fixture: &str) {
94     let mut config = DiagnosticsConfig::test_sample();
95     config.disabled.insert("inactive-code".to_string());
96     check_diagnostics_with_config(config, ra_fixture)
97 }
98 
99 #[track_caller]
check_diagnostics_with_config(config: DiagnosticsConfig, ra_fixture: &str)100 pub(crate) fn check_diagnostics_with_config(config: DiagnosticsConfig, ra_fixture: &str) {
101     let (db, files) = RootDatabase::with_many_files(ra_fixture);
102     for file_id in files {
103         let diagnostics = super::diagnostics(&db, &config, &AssistResolveStrategy::All, file_id);
104 
105         let expected = extract_annotations(&db.file_text(file_id));
106         let mut actual = diagnostics
107             .into_iter()
108             .map(|d| {
109                 let mut annotation = String::new();
110                 if let Some(fixes) = &d.fixes {
111                     assert!(!fixes.is_empty());
112                     annotation.push_str("�� ")
113                 }
114                 annotation.push_str(match d.severity {
115                     Severity::Error => "error",
116                     Severity::WeakWarning => "weak",
117                 });
118                 annotation.push_str(": ");
119                 annotation.push_str(&d.message);
120                 (d.range, annotation)
121             })
122             .collect::<Vec<_>>();
123         actual.sort_by_key(|(range, _)| range.start());
124         if expected.is_empty() {
125             // makes minicore smoke test debugable
126             for (e, _) in &actual {
127                 eprintln!(
128                     "Code in range {e:?} = {}",
129                     &db.file_text(file_id)[usize::from(e.start())..usize::from(e.end())]
130                 )
131             }
132         }
133         assert_eq!(expected, actual);
134     }
135 }
136 
137 #[test]
test_disabled_diagnostics()138 fn test_disabled_diagnostics() {
139     let mut config = DiagnosticsConfig::test_sample();
140     config.disabled.insert("unresolved-module".into());
141 
142     let (db, file_id) = RootDatabase::with_single_file(r#"mod foo;"#);
143 
144     let diagnostics = super::diagnostics(&db, &config, &AssistResolveStrategy::All, file_id);
145     assert!(diagnostics.is_empty());
146 
147     let diagnostics = super::diagnostics(
148         &db,
149         &DiagnosticsConfig::test_sample(),
150         &AssistResolveStrategy::All,
151         file_id,
152     );
153     assert!(!diagnostics.is_empty());
154 }
155 
156 #[test]
minicore_smoke_test()157 fn minicore_smoke_test() {
158     fn check(minicore: MiniCore) {
159         let source = minicore.source_code();
160         let mut config = DiagnosticsConfig::test_sample();
161         // This should be ignored since we conditionaly remove code which creates single item use with braces
162         config.disabled.insert("unnecessary-braces".to_string());
163         check_diagnostics_with_config(config, &source);
164     }
165 
166     // Checks that there is no diagnostic in minicore for each flag.
167     for flag in MiniCore::available_flags() {
168         if flag == "clone" {
169             // Clone without copy has `moved-out-of-ref`, so ignoring.
170             // FIXME: Maybe we should merge copy and clone in a single flag?
171             continue;
172         }
173         eprintln!("Checking minicore flag {flag}");
174         check(MiniCore::from_flags([flag]));
175     }
176     // And one time for all flags, to check codes which are behind multiple flags + prevent name collisions
177     eprintln!("Checking all minicore flags");
178     check(MiniCore::from_flags(MiniCore::available_flags()))
179 }
180