Skip to content

Commit 62736b6

Browse files
committed
feat(ls): show information about fields when hovering over them.
1 parent cac96e6 commit 62736b6

8 files changed

Lines changed: 99 additions & 44 deletions

File tree

ls/src/features/completion.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use crate::utils::cst_traversal::{
1818
rule_containing_token, token_at_position,
1919
};
2020

21-
use crate::utils::modules::{get_struct, ty_to_string};
21+
use crate::utils::modules::{get_type, ty_to_string};
2222

2323
const PATTERN_MODS: &[(SyntaxKind, &[&str])] = &[
2424
(
@@ -380,7 +380,7 @@ fn field_suggestions(token: &Token<Immutable>) -> Option<Vec<CompletionItem>> {
380380
_ => None,
381381
}?;
382382

383-
let current_struct = match get_struct(&token)? {
383+
let current_struct = match get_type(&token)? {
384384
Type::Struct(s) => s,
385385
_ => return None,
386386
};

ls/src/features/hover.rs

Lines changed: 68 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ use async_lsp::lsp_types::{
44
HoverContents, MarkupContent, MarkupKind, Position, Url,
55
};
66
use itertools::Itertools;
7-
7+
use yara_x::mods::reflect::Type;
88
use yara_x_parser::cst::{Immutable, Node, NodeOrToken, SyntaxKind, Utf8};
99

1010
use crate::documents::storage::DocumentStorage;
1111
use crate::utils::cst_traversal::{
12-
find_declaration, pattern_from_ident, rule_containing_token,
13-
token_at_position,
12+
find_declaration, pattern_from_ident, prev_non_trivia_token,
13+
rule_containing_token, token_at_position,
1414
};
1515

16-
use crate::utils::modules::{get_struct, ty_to_string};
16+
use crate::utils::modules::{get_type, ty_to_string};
1717

1818
/// Builder for hover Markdown representation of a rule.
1919
struct RuleHoverBuilder {
@@ -106,37 +106,71 @@ pub fn hover(
106106
}
107107
// Other identifiers.
108108
SyntaxKind::IDENT => {
109-
if let Some(yara_x::mods::reflect::Type::Func(func)) =
110-
get_struct(&token)
111-
{
112-
let documentation = func
113-
.signatures
114-
.iter()
115-
.filter_map(|signature| {
116-
signature.doc().map(|doc| {
117-
format!(
118-
"### `{}({}) -> {}`\n\n***\n\n{}\n\n***\n\n",
119-
token.text(),
120-
signature
121-
.args()
122-
.map(|(name, ty)| format!(
123-
"{}: {}",
124-
name,
125-
ty_to_string(ty)
126-
))
127-
.join(", "),
128-
ty_to_string(signature.ret_type()),
129-
doc
130-
)
131-
})
132-
})
133-
.join("\n");
134-
135-
return Some(HoverContents::Markup(MarkupContent {
136-
kind: MarkupKind::Markdown,
137-
value: documentation,
138-
}));
109+
let structure = prev_non_trivia_token(&token)
110+
.filter(|token| token.kind() == SyntaxKind::DOT)
111+
.and_then(|token| prev_non_trivia_token(&token))
112+
.and_then(|token| get_type(&token))
113+
.and_then(|ty| {
114+
if let Type::Struct(s) = ty { Some(s) } else { None }
115+
});
116+
117+
let field = structure
118+
.as_ref()
119+
.and_then(|s| s.fields().find(|f| f.name() == token.text()));
120+
121+
if let Some(field) = field {
122+
match field.ty() {
123+
Type::Func(func) => {
124+
let documentation = func
125+
.signatures
126+
.iter()
127+
.filter_map(|signature| {
128+
signature.doc().map(|doc| {
129+
format!(
130+
"### `{}({}) -> {}`\n\n***\n\n{}\n\n***\n\n",
131+
token.text(),
132+
signature
133+
.args()
134+
.map(|(arg_name, arg_ty)| format!(
135+
"{}: {}",
136+
arg_name,
137+
ty_to_string(arg_ty)
138+
))
139+
.join(", "),
140+
ty_to_string(signature.ret_type()),
141+
doc
142+
)
143+
})
144+
})
145+
.join("\n");
146+
147+
if !documentation.is_empty() {
148+
return Some(HoverContents::Markup(
149+
MarkupContent {
150+
kind: MarkupKind::Markdown,
151+
value: documentation,
152+
},
153+
));
154+
}
155+
}
156+
ty => {
157+
let mut value = format!(
158+
"### `{}: {}`",
159+
token.text(),
160+
ty_to_string(&ty)
161+
);
162+
if let Some(d) = field.doc() {
163+
value
164+
.push_str(&format!("\n\n***\n\n{}\n\n***", d));
165+
}
166+
return Some(HoverContents::Markup(MarkupContent {
167+
kind: MarkupKind::Markdown,
168+
value,
169+
}));
170+
}
171+
}
139172
}
173+
140174
if let Some((_, n)) = find_declaration(&token) {
141175
let text = n
142176
.children_with_tokens()

ls/src/features/signature_help.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
documents::storage::DocumentStorage,
55
utils::{
66
cst_traversal::{prev_non_trivia_token, token_at_position},
7-
modules::{get_struct, ty_to_string},
7+
modules::{get_type, ty_to_string},
88
},
99
};
1010
use async_lsp::lsp_types::{
@@ -54,7 +54,7 @@ pub fn signature_help(
5454

5555
let last_ident = curr?;
5656

57-
let func = match get_struct(&last_ident) {
57+
let func = match get_type(&last_ident) {
5858
Some(Type::Func(func)) => Some(func),
5959
_ => None,
6060
}?;

ls/src/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ async fn hover() {
296296
test_lsp_request::<_, HoverRequest>("hover8.yar").await;
297297
test_lsp_request::<_, HoverRequest>("hover9.yar").await;
298298
test_lsp_request::<_, HoverRequest>("hover10.yar").await;
299+
test_lsp_request::<_, HoverRequest>("hover11.yar").await;
299300
}
300301

301302
#[tokio::test]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"textDocument": {
3+
"uri": "${test_dir}/hover11.yar"
4+
},
5+
"position": {
6+
"line": 4,
7+
"character": 9
8+
}
9+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"contents": {
3+
"kind": "markdown",
4+
"value": "### `is_pe: bool`\n\n***\n\nTrue if the file is a valid PE binary.\n\n***"
5+
}
6+
}

ls/src/tests/testdata/hover11.yar

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import "pe"
2+
3+
rule test {
4+
condition:
5+
pe.is_pe
6+
}

ls/src/utils/modules.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ pub enum Segment {
2424
///
2525
/// Returns an `Option<Type>` representing the type of the structure or field
2626
/// identified by the token. Returns `None` if the type cannot be determined.
27-
pub fn get_struct(token: &Token<Immutable>) -> Option<Type> {
27+
pub fn get_type(token: &Token<Immutable>) -> Option<Type> {
2828
let mut path = Vec::new();
29-
3029
let mut curr = Some(token.clone());
30+
3131
while let Some(token) = curr {
3232
match token.kind() {
3333
SyntaxKind::IDENT => {
@@ -40,7 +40,6 @@ pub fn get_struct(token: &Token<Immutable>) -> Option<Type> {
4040
path.into_iter().rev(),
4141
);
4242
}
43-
4443
path.push(Segment::Field(token.text().to_string()));
4544
// Look for previous DOT
4645
if let Some(prev) = prev_non_trivia_token(&token)
@@ -106,6 +105,7 @@ pub fn get_struct(token: &Token<Immutable>) -> Option<Type> {
106105
}
107106
}
108107
}
108+
109109
Some(current_kind)
110110
}
111111

@@ -143,7 +143,7 @@ pub fn get_type_from_declaration(
143143
continue;
144144
}
145145

146-
let mut current_type = get_struct(&with_decl.last_token()?)?;
146+
let mut current_type = get_type(&with_decl.last_token()?)?;
147147

148148
for segment in path {
149149
match segment {
@@ -177,8 +177,7 @@ pub fn get_type_from_declaration(
177177
.into_token()?;
178178

179179
let iterable_last_token = prev_non_trivia_token(&colon)?;
180-
181-
let iterable_type = get_struct(&iterable_last_token)?;
180+
let iterable_type = get_type(&iterable_last_token)?;
182181

183182
let mut current_type = match iterable_type {
184183
Type::Array(inner) => *inner,

0 commit comments

Comments
 (0)