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