Skip to content

Commit 8efb4fe

Browse files
alanzmeta-codesync[bot]
authored andcommitted
BE: track related info in declarative tests
Summary: Our declarative tests allow us to add annotations for expected diagnostics, so we can clearly see and check what is generated. But at present there is no way to check any related information for a diagnostic. This diff adds that capability, showing related information together with the FileId and range each refers to. Reviewed By: robertoaloi Differential Revision: D87316965 fbshipit-source-id: ea7db2980b11950c3552befec7b666665ceef84b
1 parent dcf4e3a commit 8efb4fe

File tree

7 files changed

+54
-2
lines changed

7 files changed

+54
-2
lines changed

.llms/rules/elp_development.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,12 @@ directly in test code:
168168
- **Left-margin annotation**: `%%<^^^ text` - Annotation starts at `%%` position
169169
instead of first `^`
170170
- **Multiline annotations**: Use continuation lines with `%% | next line`
171+
- Continuation lines are particularly useful for diagnostics with related information:
172+
```erlang
173+
foo() -> syntax error oops.
174+
%% ^^^^^ error: P1711: syntax error before: error
175+
%% | Related info: 0:45-50 function foo/0 undefined
176+
```
171177

172178
#### Example Test Fixture
173179

crates/ide/src/diagnostics.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3433,6 +3433,9 @@ baz(1)->4.
34333433
-spec foo() -> ok.
34343434
foo( -> ok. %%
34353435
%% ^ error: W0004: Missing ')'
3436+
%% | Related info: 0:21-43 function foo/0 undefined
3437+
%% | Related info: 0:74-79 function foo/0 undefined
3438+
%% | Related info: 0:82-99 spec for undefined function foo/0
34363439
"#,
34373440
);
34383441
}
@@ -3463,6 +3466,7 @@ baz(1)->4.
34633466
34643467
foo() -> syntax error oops.
34653468
%% ^^^^^ error: P1711: syntax error before: error
3469+
%% | Related info: 0:25-30 function foo/0 undefined
34663470
"#,
34673471
);
34683472
}
@@ -3766,6 +3770,7 @@ baz(1)->4.
37663770
\~"\"\\µA\"" = \~/"\\µA"/
37673771
X = 3.
37683772
%% ^ error: P1711: syntax error before: X
3773+
%% | Related info: 0:32-37 function foo/0 undefined
37693774
"#,
37703775
);
37713776
}

crates/ide/src/diagnostics/head_mismatch.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,7 @@ mod tests {
396396
foo(0) -> 1;
397397
boo(1) -> 2.
398398
%% ^^^ 💡 error: P1700: head mismatch 'boo' vs 'foo'
399+
%% | Related info: 0:21-24 Mismatched clause name
399400
"#,
400401
);
401402
check_fix(
@@ -421,6 +422,7 @@ mod tests {
421422
ok;
422423
fooX(_X) ->
423424
%% ^^^^ 💡 error: P1700: head mismatch 'fooX' vs 'food'
425+
%% | Related info: 0:21-25 Mismatched clause name
424426
no.
425427
426428
bar() ->
@@ -450,6 +452,7 @@ mod tests {
450452
-module(main).
451453
foo(0) -> 1;
452454
%% ^^^ 💡 error: P1700: head mismatch 'foo' vs 'boo'
455+
%% | Related info: 0:37-40 Mismatched clause name
453456
boo(1) -> 2;
454457
boo(2) -> 3.
455458
"#,
@@ -478,6 +481,7 @@ mod tests {
478481
foo(0) -> 1;
479482
foo(1,0) -> 2.
480483
%% ^^^^^^^^^^^^^ error: P1700: head arity mismatch 2 vs 1
484+
%% | Related info: 0:21-32 Mismatched clause
481485
"#,
482486
);
483487
}
@@ -490,6 +494,7 @@ mod tests {
490494
foo(2,0) -> 3;
491495
foo(0) -> 1;
492496
%% ^^^^^^^^^^^ error: P1700: head arity mismatch 1 vs 2
497+
%% | Related info: 0:21-34 Mismatched clause
493498
foo(1,0) -> 2.
494499
"#,
495500
);
@@ -516,6 +521,7 @@ mod tests {
516521
(0) -> ok;
517522
A(N) -> ok
518523
%% ^ 💡 error: P1700: head mismatch 'A' vs ''
524+
%% | Related info: 0:44-53 Mismatched clause name
519525
end,
520526
F().
521527
"#,

crates/ide/src/diagnostics/misspelled_attribute.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ mod tests {
164164
-module(main).
165165
-dyalizer({nowarn_function, f/0}).
166166
%%% ^^^^^^^^ 💡 error: W0013: misspelled attribute, saw 'dyalizer' but expected 'dialyzer'
167+
%%% | Related info: 0:22-30 Misspelled attribute
167168
"#,
168169
);
169170
check_fix(
@@ -224,6 +225,7 @@ mod tests {
224225
-module(main).
225226
-module_doc """
226227
%%% ^^^^^^^^^^ 💡 error: W0013: misspelled attribute, saw 'module_doc' but expected 'moduledoc'
228+
%%% | Related info: 0:24-34 Misspelled attribute
227229
Hola
228230
""".
229231
"#,
@@ -237,6 +239,7 @@ mod tests {
237239
-module(main).
238240
-docs """
239241
%%% ^^^^ 💡 error: W0013: misspelled attribute, saw 'docs' but expected 'doc'
242+
%%% | Related info: 0:24-28 Misspelled attribute
240243
Hola
241244
""".
242245
foo() -> ok.

crates/ide/src/diagnostics/unused_include.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,7 @@ foo() -> ok.
581581
%% The following shows up as a wild attribute, which we regard as being used.
582582
-defin e(X, 1).
583583
%% ^^^^^ 💡 error: W0013: misspelled attribute, saw 'defin' but expected 'define'
584+
%% | Related info: 1:82-87 Misspelled attribute
584585
585586
-def ine(Y, 2).
586587
"#,

crates/ide/src/tests.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,23 @@ fn convert_diagnostics_to_annotations(diagnostics: Vec<Diagnostic>) -> Vec<(Text
401401
annotation.push_str(&d.code.as_code());
402402
annotation.push_str(": ");
403403
annotation.push_str(&convert_diagnostic_message(&d));
404+
405+
// Append related info to the annotation
406+
if let Some(related_info) = &d.related_info {
407+
// Sort related info alphabetically by message for consistent test output
408+
let mut sorted_info = related_info.clone();
409+
sorted_info.sort_by(|a, b| a.message.cmp(&b.message));
410+
for info in sorted_info {
411+
annotation.push_str(&format!(
412+
"\nRelated info: {}:{}-{} {}",
413+
info.file_id.index(),
414+
u32::from(info.range.start()),
415+
u32::from(info.range.end()),
416+
info.message
417+
));
418+
}
419+
}
420+
404421
(d.range, annotation)
405422
})
406423
.collect::<Vec<_>>();

crates/project_model/src/test_fixture.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,14 @@ pub fn extract_tags(mut text: &str, tag: &str) -> (Vec<(TextRange, Option<String
472472
/// %% | second line
473473
/// ```
474474
///
475+
/// This is useful for representing diagnostics with related information:
476+
///
477+
/// ```not_rust
478+
/// foo() -> syntax error oops.
479+
/// %% ^^^^^ error: P1711: syntax error before: error
480+
/// %% | Related info: 0:25-30 function foo/0 undefined
481+
/// ```
482+
///
475483
/// Annotations point to the last line that actually was long enough for the
476484
/// range, not counting annotations themselves. So overlapping annotations are
477485
/// possible:
@@ -551,10 +559,16 @@ pub fn extract_annotations(text: &str) -> (Vec<(TextRange, String)>, String) {
551559
if !res.is_empty() {
552560
offset += annotation_offset;
553561
this_line_annotations.push((offset, res.len() - 1));
554-
let &(_, idx) = prev_line_annotations
562+
// Try to find a previous annotation at the same offset
563+
let idx = if let Some(&(_, idx)) = prev_line_annotations
555564
.iter()
556565
.find(|&&(off, _idx)| off == offset)
557-
.unwrap();
566+
{
567+
idx
568+
} else {
569+
// If no exact offset match, append to the most recent annotation
570+
res.len() - 1
571+
};
558572
res[idx].1.push('\n');
559573
res[idx].1.push_str(&content);
560574
}

0 commit comments

Comments
 (0)