Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions ls/editors/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@
}
}
}
},
"YARA.ruleNameValidation": {
"type": "string",
"default": "",
"description": "A regular expression that all rule names must match."
}
}
},
Expand Down
38 changes: 32 additions & 6 deletions ls/src/features/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use dashmap::mapref::one::Ref;
use serde::{Deserialize, Serialize};

use crate::documents::{document::Document, storage::DocumentStorage};
use crate::server::MetadataValidationRule;

#[cfg(feature = "full-compiler")]
use yara_x::linters;
Expand All @@ -27,12 +26,33 @@ pub struct Patch {
pub replacement: String,
}

/// Rule that describes a how to validate a metadata entry in a rule.
#[derive(Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct MetadataValidationRule {
/// Metadata identifier
pub identifier: String,
/// Whether the metadata entry is required or not.
#[serde(default)]
pub required: bool,
/// Type of the metadata entry.
#[serde(rename = "type")]
pub ty: Option<String>,
}

#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Settings {
pub metadata_validation: Vec<MetadataValidationRule>,
pub rule_name_validation: Option<String>,
}

/// Returns a diagnostic vector for the given source code.
#[allow(unused_variables)]
pub fn diagnostics(
documents: Arc<DocumentStorage>,
uri: Url,
meta_validation_rules: Vec<MetadataValidationRule>,
settings: Settings,
) -> Vec<Diagnostic> {
#[allow(unused_mut)]
let mut diagnostics: Vec<Diagnostic> = Vec::new();
Expand All @@ -41,29 +61,35 @@ pub fn diagnostics(

if let Some(doc) = doc {
#[cfg(feature = "full-compiler")]
diagnostics.extend(compiler_diagnostics(doc, meta_validation_rules));
diagnostics.extend(compiler_diagnostics(doc, settings));
}

diagnostics
}

/// Return diagnostic vector for the given source code.
///
/// This function compiles the source code using the full YARA-X compiler
/// This function compiles the source code using the full YARA-X 'compiler'
/// and collects all errors and warnings as LSP diagnostics. This provides
/// comprehensive feedback including type checking, semantic analysis,
/// and pattern validation - not just syntax errors.
#[cfg(feature = "full-compiler")]
pub fn compiler_diagnostics(
document: Ref<'_, Url, Document>,
metadata_validation_rules: Vec<MetadataValidationRule>,
settings: Settings,
) -> Vec<Diagnostic> {
let source_code = SourceCode::from(document.text.as_str())
.with_origin(document.uri.clone());

let mut compiler = Compiler::new();

for validation_rule in metadata_validation_rules {
if let Some(regex) = settings.rule_name_validation {
if let Ok(linter) = linters::rule_name(regex) {
compiler.add_linter(linter);
}
}

for validation_rule in settings.metadata_validation {
let mut linter = linters::metadata(&validation_rule.identifier)
.required(validation_rule.required);

Expand Down
75 changes: 35 additions & 40 deletions ls/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@ use async_lsp::lsp_types::{
use async_lsp::router::Router;
use async_lsp::{ClientSocket, LanguageClient, LanguageServer, ResponseError};
use futures::future::BoxFuture;
use serde::Deserialize;
use serde_json::from_value;

use crate::documents::storage::DocumentStorage;
use crate::features::code_action::code_actions;
use crate::features::completion::completion;
use crate::features::diagnostics::diagnostics;
use crate::features::diagnostics::{
diagnostics, MetadataValidationRule, Settings,
};
use crate::features::document_highlight::document_highlight;
use crate::features::document_symbol::document_symbol;
use crate::features::formatting::formatting;
Expand All @@ -55,20 +57,6 @@ use crate::features::semantic_tokens::{
semantic_tokens, SEMANTIC_TOKEN_MODIFIERS, SEMANTIC_TOKEN_TYPES,
};

/// Rule that describes a how to validate a metadata entry in a rule.
#[derive(Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct MetadataValidationRule {
/// Metadata identifier
pub identifier: String,
/// Whether the metadata entry is required or not.
#[serde(default)]
pub required: bool,
/// Type of the metadata entry.
#[serde(rename = "type")]
pub ty: Option<String>,
}

/// Represents a YARA language server.
pub struct YARALanguageServer {
/// Client socket for communication with the Development Tool.
Expand Down Expand Up @@ -426,17 +414,15 @@ impl LanguageServer for YARALanguageServer {
let client = self.client.clone();

Box::pin(async move {
let meta_specs =
Self::get_meta_validation_rules(client.clone(), uri.clone())
.await;
let settings = Self::get_settings(client, uri.clone()).await;

Ok(DocumentDiagnosticReportResult::Report(
async_lsp::lsp_types::DocumentDiagnosticReport::Full(
RelatedFullDocumentDiagnosticReport {
full_document_diagnostic_report:
FullDocumentDiagnosticReport {
result_id: None,
items: diagnostics(documents, uri, meta_specs),
items: diagnostics(documents, uri, settings),
},
related_documents: None,
},
Expand Down Expand Up @@ -563,23 +549,39 @@ impl YARALanguageServer {
})
}

async fn get_meta_validation_rules(
async fn get_settings(
mut client: ClientSocket,
scope_uri: Url,
) -> Vec<MetadataValidationRule> {
) -> Settings {
client
.configuration(ConfigurationParams {
items: vec![ConfigurationItem {
scope_uri: Some(scope_uri),
section: Some("YARA.metadataValidation".to_string()),
}],
items: vec![
ConfigurationItem {
scope_uri: Some(scope_uri.clone()),
section: Some("YARA.metadataValidation".to_string()),
},
ConfigurationItem {
scope_uri: Some(scope_uri),
section: Some("YARA.ruleNameValidation".to_string()),
},
],
})
.await
.ok()
.and_then(|mut v| v.pop())
.and_then(|value| {
serde_json::from_value::<Vec<MetadataValidationRule>>(value)
.ok()
.map(|mut v| {
let rule_name_validation = v
.pop()
.and_then(|value| from_value::<Option<String>>(value).ok())
.unwrap_or_default();

let metadata_validation = v
.pop()
.and_then(|value| {
from_value::<Vec<MetadataValidationRule>>(value).ok()
})
.unwrap_or_default();

Settings { metadata_validation, rule_name_validation }
})
.unwrap_or_default()
}
Expand All @@ -592,19 +594,12 @@ impl YARALanguageServer {
let uri = uri.clone();

tokio::spawn(async move {
let meta_validation_rules = Self::get_meta_validation_rules(
client.clone(),
uri.clone(),
)
.await;
let settings =
Self::get_settings(client.clone(), uri.clone()).await;

client.publish_diagnostics(PublishDiagnosticsParams {
uri: uri.clone(),
diagnostics: diagnostics(
documents,
uri,
meta_validation_rules,
),
diagnostics: diagnostics(documents, uri, settings),
version: None,
})
});
Expand Down
Loading