diff --git a/Cargo.lock b/Cargo.lock index 28d745078..39887b477 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -454,6 +454,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-link", ] @@ -5194,6 +5195,7 @@ version = "1.14.0" dependencies = [ "async-lsp", "bitflags 2.11.0", + "chrono", "dashmap", "futures", "goldenfile", diff --git a/ls/Cargo.toml b/ls/Cargo.toml index 5ea4c5acd..5e97e750f 100644 --- a/ls/Cargo.toml +++ b/ls/Cargo.toml @@ -35,6 +35,7 @@ default = ["full-compiler"] [dependencies] bitflags = { workspace = true } +chrono = { workspace = true, features = ["serde"] } dashmap = { version = "6.1.0" } futures = "0.3.32" regex = { workspace = true } diff --git a/ls/editors/code/README.md b/ls/editors/code/README.md index 3a1d7fe77..933973956 100644 --- a/ls/editors/code/README.md +++ b/ls/editors/code/README.md @@ -82,8 +82,12 @@ the following properties: * `identifier` (string, required): The name of the metadata field to validate (e.g., `author`, `version`). * `required` (boolean, optional): If `true`, the metadata field must be present in the rule. Defaults to `false`. * `type` (string, optional): Specifies the expected type of the metadata value. Valid values are - `"string"`, `"integer"`, `"float"`, and `"bool"`. If the value does not match the specified type, a warning will - be generated. + `"string"`, `"integer"`, `"float"`, `"bool"`, and `"date"`. If the value does not match the specified type, a + warning will be generated. +* `format` (string, optional): When `type` is `"date"`, this property specifies the expected format of the + date string. The format string supports specifiers like `%Y` (year), `%m` (month), `%d` (day), `%H` (hour), + `%M` (minute), and `%S` (second). For example, for a date like `"2024-01-25"`, the format should be `"%Y-%m-%d"`. + For more information see: https://docs.rs/chrono/latest/chrono/format/strftime/index.html For accessing these settings go to the Settings diff --git a/ls/editors/code/package.json b/ls/editors/code/package.json index 1a67fd37d..6e4d4a4bf 100644 --- a/ls/editors/code/package.json +++ b/ls/editors/code/package.json @@ -94,8 +94,12 @@ }, "type": { "type": "string", - "enum": ["string", "integer", "float", "bool"], + "enum": ["string", "integer", "float", "bool", "date"], "description": "The expected type of the metadata value." + }, + "format": { + "type": "string", + "description": "The expected format of the date string (e.g., `%Y-%m-%d`)." } } } diff --git a/ls/src/configuration.rs b/ls/src/configuration.rs index f2309208c..93f584211 100644 --- a/ls/src/configuration.rs +++ b/ls/src/configuration.rs @@ -51,4 +51,6 @@ pub struct MetadataValidationRule { /// Type of the metadata entry. #[serde(rename = "type")] pub ty: Option, + /// Format of the metadata entry, if type is "date". + pub format: Option, } diff --git a/ls/src/features/diagnostics.rs b/ls/src/features/diagnostics.rs index 7a72c2652..d1316885b 100644 --- a/ls/src/features/diagnostics.rs +++ b/ls/src/features/diagnostics.rs @@ -10,6 +10,7 @@ use dashmap::mapref::one::Ref; use serde::{Deserialize, Serialize}; use crate::configuration::MetadataValidationRule; +use chrono::NaiveDate; #[cfg(feature = "full-compiler")] use crate::documents::document::Document; @@ -88,44 +89,94 @@ pub fn compiler_diagnostics( } for validation_rule in metadata_validation { - let mut linter = linters::metadata(&validation_rule.identifier) + let linter = linters::metadata(&validation_rule.identifier) .required(validation_rule.required); if let Some(ty) = &validation_rule.ty { - let predicate = match ty.as_str() { - "string" => |meta: &yara_x_parser::ast::Meta| { - matches!( - meta.value, - yara_x_parser::ast::MetaValue::String(_) - ) - }, - "integer" => |meta: &yara_x_parser::ast::Meta| { - matches!( - meta.value, - yara_x_parser::ast::MetaValue::Integer(_) - ) - }, - "float" => |meta: &yara_x_parser::ast::Meta| { - matches!( - meta.value, - yara_x_parser::ast::MetaValue::Float(_) - ) - }, - "bool" => |meta: &yara_x_parser::ast::Meta| { - matches!( - meta.value, - yara_x_parser::ast::MetaValue::Bool(_) - ) - }, - _ => continue, + match ty.as_str() { + "string" => { + compiler.add_linter(linter.validator( + |meta| { + matches!( + meta.value, + yara_x_parser::ast::MetaValue::String(_) + ) + }, + format!( + "`{}` must be a `string`", + validation_rule.identifier + ), + )); + } + "integer" => { + compiler.add_linter(linter.validator( + |meta| { + matches!( + meta.value, + yara_x_parser::ast::MetaValue::Integer(_) + ) + }, + format!( + "`{}` must be a `integer`", + validation_rule.identifier + ), + )); + } + "float" => { + compiler.add_linter(linter.validator( + |meta| { + matches!( + meta.value, + yara_x_parser::ast::MetaValue::Float(_) + ) + }, + format!( + "`{}` must be a `float`", + validation_rule.identifier + ), + )); + } + "bool" => { + compiler.add_linter(linter.validator( + |meta| { + matches!( + meta.value, + yara_x_parser::ast::MetaValue::Float(_) + ) + }, + format!( + "`{}` must be a `bool`", + validation_rule.identifier + ), + )); + } + "date" => { + let format = validation_rule + .format + .as_deref() + .unwrap_or("%Y-%m-%d"); + + compiler.add_linter(linter.validator( + |meta| { + if let yara_x_parser::ast::MetaValue::String( + value, + ) = &meta.value + { + NaiveDate::parse_from_str(value.0, format) + .is_ok() + } else { + false + } + }, + format!( + "`{}` must be a `date` with format `{}`", + validation_rule.identifier, format + ), + )); + } + _ => {} }; - linter = linter.validator( - predicate, - format!("`{}` must be a `{}`", validation_rule.identifier, ty), - ); } - - compiler.add_linter(linter); } // VSCode don't handle well error messages with too many columns.