diff --git a/crates/rome_analyze/src/rule.rs b/crates/rome_analyze/src/rule.rs index 37ead1e88f9..6dbd26250fe 100644 --- a/crates/rome_analyze/src/rule.rs +++ b/crates/rome_analyze/src/rule.rs @@ -6,7 +6,7 @@ use crate::{ }; use rome_console::fmt::Display; use rome_console::{markup, MarkupBuf}; -use rome_deserialize::json::{deserialize_from_json, JsonDeserialize, VisitJsonNode}; +use rome_deserialize::json::{deserialize_from_json_str, JsonDeserialize, VisitJsonNode}; use rome_deserialize::Deserialized; use rome_diagnostics::advice::CodeSuggestionAdvice; use rome_diagnostics::location::AsSpan; @@ -244,7 +244,7 @@ impl_group_language!( pub trait DeserializableRuleOptions: Default + Sized + JsonDeserialize + VisitJsonNode { fn from(value: String) -> Deserialized { - deserialize_from_json(&value) + deserialize_from_json_str(&value) } } diff --git a/crates/rome_deserialize/src/json.rs b/crates/rome_deserialize/src/json.rs index 4a251ddf87d..e7de2ea1c58 100644 --- a/crates/rome_deserialize/src/json.rs +++ b/crates/rome_deserialize/src/json.rs @@ -407,7 +407,8 @@ pub fn with_only_known_variants( }) } -/// It attempts to parse and deserialize a source file in JSON. +/// It attempts to parse and deserialize a source file in JSON. Diagnostics from the parse phase +/// are consumed and joined with the diagnostics emitted during the deserialization. /// /// The data structure that needs to be deserialized needs to implement three important traits: /// - [Default], to create a first instance of the data structure; @@ -420,7 +421,7 @@ pub fn with_only_known_variants( /// /// ``` /// use rome_deserialize::{DeserializationDiagnostic, VisitNode, Deserialized}; -/// use rome_deserialize::json::deserialize_from_json; +/// use rome_deserialize::json::deserialize_from_json_str; /// use rome_deserialize::json::{with_only_known_variants, has_only_known_keys, JsonDeserialize, VisitJsonNode}; /// use rome_json_syntax::{JsonLanguage, JsonSyntaxNode}; /// use rome_json_syntax::JsonRoot; @@ -476,7 +477,7 @@ pub fn with_only_known_variants( /// /// # fn main() -> Result<(), DeserializationDiagnostic> { /// let source = r#"{ "lorem": true }"#; -/// let deserialized = deserialize_from_json::(&source); +/// let deserialized = deserialize_from_json_str::(&source); /// assert!(!deserialized.has_errors()); /// assert_eq!(deserialized.into_deserialized(), NewConfiguration { lorem: true }); /// # Ok(()) @@ -484,7 +485,7 @@ pub fn with_only_known_variants( /// /// /// ``` -pub fn deserialize_from_json(source: &str) -> Deserialized +pub fn deserialize_from_json_str(source: &str) -> Deserialized where Output: Default + VisitJsonNode + JsonDeserialize, { @@ -508,3 +509,17 @@ where deserialized: output, } } + +/// Attempts to deserialize a JSON AST, given the `Output`. +pub fn deserialize_from_json_ast(parse: JsonRoot) -> Deserialized +where + Output: Default + VisitJsonNode + JsonDeserialize, +{ + let mut output = Output::default(); + let mut diagnostics = vec![]; + Output::deserialize_from_ast(parse, &mut output, &mut diagnostics); + Deserialized { + diagnostics: diagnostics.into_iter().map(Error::from).collect::>(), + deserialized: output, + } +} diff --git a/crates/rome_js_analyze/src/semantic_analyzers/nursery/use_exhaustive_dependencies.rs b/crates/rome_js_analyze/src/semantic_analyzers/nursery/use_exhaustive_dependencies.rs index 0fad6063163..82bb031e0b4 100644 --- a/crates/rome_js_analyze/src/semantic_analyzers/nursery/use_exhaustive_dependencies.rs +++ b/crates/rome_js_analyze/src/semantic_analyzers/nursery/use_exhaustive_dependencies.rs @@ -5,7 +5,7 @@ use rome_analyze::{ }; use rome_console::markup; use rome_deserialize::json::{ - deserialize_from_json, has_only_known_keys, JsonDeserialize, VisitJsonNode, + deserialize_from_json_str, has_only_known_keys, JsonDeserialize, VisitJsonNode, }; use rome_deserialize::{DeserializationDiagnostic, Deserialized, VisitNode}; use rome_js_semantic::{Capture, SemanticModel}; @@ -253,7 +253,7 @@ impl JsonDeserialize for HooksOptions { impl DeserializableRuleOptions for HooksOptions { fn from(value: String) -> Deserialized { - deserialize_from_json(&value) + deserialize_from_json_str(&value) } } diff --git a/crates/rome_lsp/tests/server.rs b/crates/rome_lsp/tests/server.rs index cbbc0038d99..9efa3be7d07 100644 --- a/crates/rome_lsp/tests/server.rs +++ b/crates/rome_lsp/tests/server.rs @@ -204,6 +204,27 @@ impl Server { .await } + /// Opens a document with given contents and given name. The name must contain the extension too + async fn open_named_document( + &mut self, + text: impl Display, + document_name: Url, + language: impl Display, + ) -> Result<()> { + self.notify( + "textDocument/didOpen", + DidOpenTextDocumentParams { + text_document: TextDocumentItem { + uri: document_name, + language_id: language.to_string(), + version: 0, + text: text.to_string(), + }, + }, + ) + .await + } + async fn change_document( &mut self, version: i32, @@ -754,6 +775,73 @@ async fn pull_quick_fixes() -> Result<()> { Ok(()) } +#[tokio::test] +async fn pull_diagnostics_for_rome_json() -> Result<()> { + let factory = ServerFactory::default(); + let (service, client) = factory.create().into_inner(); + let (stream, sink) = client.split(); + let mut server = Server::new(service); + + let (sender, mut receiver) = channel(CHANNEL_BUFFER_SIZE); + let reader = tokio::spawn(client_handler(stream, sink, sender)); + + server.initialize().await?; + server.initialized().await?; + + let incorrect_config = r#"{ + "formatter": { + "indentStyle": "magic" + } + }"#; + server + .open_named_document(incorrect_config, url!("rome.json"), "json") + .await?; + + let notification = tokio::select! { + msg = receiver.next() => msg, + _ = sleep(Duration::from_secs(1)) => { + panic!("timed out waiting for the server to send diagnostics") + } + }; + + assert_eq!( + notification, + Some(ServerNotification::PublishDiagnostics( + PublishDiagnosticsParams { + uri: url!("rome.json"), + version: Some(0), + diagnostics: vec![lsp::Diagnostic { + range: lsp::Range { + start: lsp::Position { + line: 2, + character: 27, + }, + end: lsp::Position { + line: 2, + character: 34, + }, + }, + severity: Some(lsp::DiagnosticSeverity::ERROR), + code: Some(lsp::NumberOrString::String(String::from("configuration",))), + code_description: None, + source: Some(String::from("rome")), + message: String::from("Found an unknown value `magic`",), + related_information: None, + tags: None, + data: None, + }], + } + )) + ); + + server.close_document().await?; + + server.shutdown().await?; + reader.abort(); + + Ok(()) +} + #[tokio::test] async fn pull_refactors() -> Result<()> { let factory = ServerFactory::default(); diff --git a/crates/rome_service/src/configuration/diagnostics.rs b/crates/rome_service/src/configuration/diagnostics.rs index ca423ca62f5..1602a3d49c7 100644 --- a/crates/rome_service/src/configuration/diagnostics.rs +++ b/crates/rome_service/src/configuration/diagnostics.rs @@ -215,7 +215,7 @@ pub struct InvalidIgnorePattern { mod test { use crate::configuration::diagnostics::ConfigurationDiagnostic; use crate::{Configuration, MatchOptions, Matcher}; - use rome_deserialize::json::deserialize_from_json; + use rome_deserialize::json::deserialize_from_json_str; use rome_diagnostics::{print_diagnostic_to_string, DiagnosticExt, Error}; fn snap_diagnostic(test_name: &str, diagnostic: Error) { @@ -268,7 +268,7 @@ mod test { #[test] fn deserialization_error() { let content = "{ \n\n\"formatter\" }"; - let result = deserialize_from_json::(content); + let result = deserialize_from_json_str::(content); assert!(result.has_errors()); for diagnostic in result.into_diagnostics() { @@ -291,6 +291,6 @@ mod test { } } }"#; - let _result = deserialize_from_json::(content).into_deserialized(); + let _result = deserialize_from_json_str::(content).into_deserialized(); } } diff --git a/crates/rome_service/src/configuration/mod.rs b/crates/rome_service/src/configuration/mod.rs index c013eae6902..6dc073d63c2 100644 --- a/crates/rome_service/src/configuration/mod.rs +++ b/crates/rome_service/src/configuration/mod.rs @@ -32,7 +32,7 @@ pub use formatter::{FormatterConfiguration, PlainIndentStyle}; pub use javascript::{JavascriptConfiguration, JavascriptFormatter}; pub use linter::{LinterConfiguration, RuleConfiguration, Rules}; use rome_analyze::{AnalyzerConfiguration, AnalyzerRules}; -use rome_deserialize::json::deserialize_from_json; +use rome_deserialize::json::deserialize_from_json_str; use rome_deserialize::Deserialized; use rome_js_analyze::metadata; use rome_json_formatter::context::JsonFormatOptions; @@ -208,7 +208,7 @@ pub fn load_config( ); } - let deserialized = deserialize_from_json::(&buffer) + let deserialized = deserialize_from_json_str::(&buffer) .with_file_path(&configuration_path.display().to_string()); Ok(Some(deserialized)) } diff --git a/crates/rome_service/src/file_handlers/json.rs b/crates/rome_service/src/file_handlers/json.rs index 20d6e6a2b23..03260b0dd8c 100644 --- a/crates/rome_service/src/file_handlers/json.rs +++ b/crates/rome_service/src/file_handlers/json.rs @@ -8,10 +8,11 @@ use crate::settings::{ FormatSettings, Language, LanguageSettings, LanguagesSettings, SettingsHandle, }; use crate::workspace::{GetSyntaxTreeResult, PullActionsResult}; -use crate::{Rules, WorkspaceError}; +use crate::{Configuration, Rules, WorkspaceError}; +use rome_deserialize::json::deserialize_from_json_ast; use rome_diagnostics::{Diagnostic, Severity}; use rome_formatter::{FormatError, Printed}; -use rome_fs::RomePath; +use rome_fs::{RomePath, CONFIG_NAME}; use rome_json_formatter::context::JsonFormatOptions; use rome_json_formatter::format_node; use rome_json_syntax::{JsonLanguage, JsonRoot, JsonSyntaxNode}; @@ -176,7 +177,21 @@ fn format_on_type( } fn lint(params: LintParams) -> LintResults { - let diagnostics = params.parse.into_diagnostics(); + let root: JsonRoot = params.parse.tree(); + let mut diagnostics = params.parse.into_diagnostics(); + + // if we're parsing the `rome.json` file, we deserialize it, so we can emit diagnostics for + // malformed configuration + if params.path.ends_with(CONFIG_NAME) { + let deserialized = deserialize_from_json_ast::(root); + diagnostics.extend( + deserialized + .into_diagnostics() + .into_iter() + .map(rome_diagnostics::serde::Diagnostic::new) + .collect::>(), + ); + } let diagnostic_count = diagnostics.len() as u64; let errors = diagnostics diff --git a/crates/rome_service/src/file_handlers/mod.rs b/crates/rome_service/src/file_handlers/mod.rs index 3815174d6c1..ff154cf2601 100644 --- a/crates/rome_service/src/file_handlers/mod.rs +++ b/crates/rome_service/src/file_handlers/mod.rs @@ -184,6 +184,7 @@ pub(crate) struct LintParams<'a> { pub(crate) rules: Option<&'a Rules>, pub(crate) settings: SettingsHandle<'a>, pub(crate) max_diagnostics: u64, + pub(crate) path: &'a RomePath, } pub(crate) struct LintResults { diff --git a/crates/rome_service/src/workspace/server.rs b/crates/rome_service/src/workspace/server.rs index 4161294543a..ad6d279da05 100644 --- a/crates/rome_service/src/workspace/server.rs +++ b/crates/rome_service/src/workspace/server.rs @@ -381,6 +381,7 @@ impl Workspace for WorkspaceServer { rules, settings: self.settings(), max_diagnostics: params.max_diagnostics, + path: ¶ms.path, }); ( diff --git a/crates/rome_service/tests/spec_tests.rs b/crates/rome_service/tests/spec_tests.rs index a23f0cf4925..9eb8832bc38 100644 --- a/crates/rome_service/tests/spec_tests.rs +++ b/crates/rome_service/tests/spec_tests.rs @@ -1,4 +1,4 @@ -use rome_deserialize::json::deserialize_from_json; +use rome_deserialize::json::deserialize_from_json_str; use rome_diagnostics::{print_diagnostic_to_string, DiagnosticExt}; use rome_service::Configuration; use std::ffi::OsStr; @@ -14,7 +14,7 @@ fn run_invalid_configurations(input: &'static str, _: &str, _: &str, _: &str) { .unwrap_or_else(|err| panic!("failed to read {:?}: {:?}", input_file, err)); let result = match extension { - "json" => deserialize_from_json::(input_code.as_str()), + "json" => deserialize_from_json_str::(input_code.as_str()), _ => { panic!("Extension not supported"); }