From 77ba14bbf0a5aae64cf736f7e68201be3e535c6f Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Thu, 19 Feb 2026 11:44:38 +0100 Subject: [PATCH] feat(ls): allow the user to specify a regular expression that all rule identifiers must match. If some rule has an identifier that doesn't match the regexp, a warning is shown. --- ls/editors/code/package.json | 5 +++ ls/src/features/diagnostics.rs | 38 ++++++++++++++--- ls/src/server.rs | 75 ++++++++++++++++------------------ 3 files changed, 72 insertions(+), 46 deletions(-) diff --git a/ls/editors/code/package.json b/ls/editors/code/package.json index b683b270b..7f539ef16 100644 --- a/ls/editors/code/package.json +++ b/ls/editors/code/package.json @@ -99,6 +99,11 @@ } } } + }, + "YARA.ruleNameValidation": { + "type": "string", + "default": "", + "description": "A regular expression that all rule names must match." } } }, diff --git a/ls/src/features/diagnostics.rs b/ls/src/features/diagnostics.rs index e25955368..34b4c9323 100644 --- a/ls/src/features/diagnostics.rs +++ b/ls/src/features/diagnostics.rs @@ -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; @@ -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, +} + +#[derive(Debug, Clone, Default, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Settings { + pub metadata_validation: Vec, + pub rule_name_validation: Option, +} + /// Returns a diagnostic vector for the given source code. #[allow(unused_variables)] pub fn diagnostics( documents: Arc, uri: Url, - meta_validation_rules: Vec, + settings: Settings, ) -> Vec { #[allow(unused_mut)] let mut diagnostics: Vec = Vec::new(); @@ -41,7 +61,7 @@ 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 @@ -49,21 +69,27 @@ pub fn 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, + settings: Settings, ) -> Vec { 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); diff --git a/ls/src/server.rs b/ls/src/server.rs index dd66fc4b4..7b7b06526 100644 --- a/ls/src/server.rs +++ b/ls/src/server.rs @@ -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; @@ -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, -} - /// Represents a YARA language server. pub struct YARALanguageServer { /// Client socket for communication with the Development Tool. @@ -426,9 +414,7 @@ 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( @@ -436,7 +422,7 @@ impl LanguageServer for YARALanguageServer { full_document_diagnostic_report: FullDocumentDiagnosticReport { result_id: None, - items: diagnostics(documents, uri, meta_specs), + items: diagnostics(documents, uri, settings), }, related_documents: None, }, @@ -563,23 +549,39 @@ impl YARALanguageServer { }) } - async fn get_meta_validation_rules( + async fn get_settings( mut client: ClientSocket, scope_uri: Url, - ) -> Vec { + ) -> 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::>(value) - .ok() + .map(|mut v| { + let rule_name_validation = v + .pop() + .and_then(|value| from_value::>(value).ok()) + .unwrap_or_default(); + + let metadata_validation = v + .pop() + .and_then(|value| { + from_value::>(value).ok() + }) + .unwrap_or_default(); + + Settings { metadata_validation, rule_name_validation } }) .unwrap_or_default() } @@ -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, }) });