From 4a4f985f20ac6bb8e41aeb84849f56c464d41c02 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Mon, 29 Aug 2022 13:59:23 +0100 Subject: [PATCH] feat(npm): allow to set global settings (#3123) --- .cargo/config.toml | 1 + .github/workflows/runtime.yml | 4 + crates/rome_cli/src/commands/check.rs | 13 +- crates/rome_cli/src/commands/ci.rs | 13 +- crates/rome_cli/src/commands/format.rs | 12 +- crates/rome_js_syntax/src/suppression.rs | 2 +- crates/rome_lsp/src/config.rs | 20 -- crates/rome_lsp/src/session.rs | 21 +- .../src/configuration/javascript.rs | 80 ++++---- crates/rome_service/src/settings.rs | 8 +- crates/rome_service/src/workspace.rs | 5 +- crates/rome_service/src/workspace/server.rs | 2 +- crates/rome_service/src/workspace_types.rs | 78 +++++--- crates/rome_wasm/build.rs | 2 +- editors/vscode/configuration_schema.json | 5 +- npm/backend-jsonrpc/src/workspace.ts | 187 +++++++++++++++--- npm/rome/package.json | 3 +- npm/rome/pnpm-lock.yaml | 159 --------------- npm/rome/src/daemon.ts | 37 ++++ npm/rome/src/index.ts | 58 ++++-- npm/rome/src/nodeWasm.ts | 20 ++ npm/rome/src/utils.ts | 20 ++ .../__snapshots__/formatContent.test.mjs.snap | 39 ++++ npm/rome/tests/wasm/formatContent.test.mjs | 129 ++++++++++++ npm/rome/tests/wasm/formatFiles.test.mjs | 29 +++ website/playground/package.json | 2 +- website/playground/src/romeWorker.ts | 30 ++- xtask/codegen/src/generate_bindings.rs | 13 +- 28 files changed, 642 insertions(+), 350 deletions(-) create mode 100644 npm/rome/src/utils.ts create mode 100644 npm/rome/tests/wasm/__snapshots__/formatContent.test.mjs.snap create mode 100644 npm/rome/tests/wasm/formatContent.test.mjs create mode 100644 npm/rome/tests/wasm/formatFiles.test.mjs diff --git a/.cargo/config.toml b/.cargo/config.toml index ffd3538ddce..6dbe2cd0077 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -19,6 +19,7 @@ documentation = """ -p rome_console \ -p rome_*parser \ -p rome_rowan + -p rome_bin --no-deps """ bench_parser = "run -p xtask_bench --release -- --feature parser" diff --git a/.github/workflows/runtime.yml b/.github/workflows/runtime.yml index 8db2db63200..42f227e98f9 100644 --- a/.github/workflows/runtime.yml +++ b/.github/workflows/runtime.yml @@ -27,6 +27,10 @@ jobs: uses: actions/checkout@v3 with: submodules: false + - name: Install wasm-pack + uses: jetli/wasm-pack-action@v0.3.0 + with: + version: 'latest' - name: Cache pnpm modules uses: actions/cache@v3 with: diff --git a/crates/rome_cli/src/commands/check.rs b/crates/rome_cli/src/commands/check.rs index 56f266a86bd..e837c4c6a2d 100644 --- a/crates/rome_cli/src/commands/check.rs +++ b/crates/rome_cli/src/commands/check.rs @@ -32,18 +32,13 @@ pub(crate) fn check(mut session: CliSession) -> Result<(), Termination> { }; if let Some(configuration) = configuration { - workspace_settings.merge_with_configuration(configuration) + session + .app + .workspace + .update_settings(UpdateSettingsParams { configuration })?; } - apply_format_settings_from_cli(&mut session, &mut workspace_settings)?; - session - .app - .workspace - .update_settings(UpdateSettingsParams { - settings: workspace_settings, - })?; - let apply = session.args.contains("--apply"); let apply_suggested = session.args.contains("--apply-suggested"); diff --git a/crates/rome_cli/src/commands/ci.rs b/crates/rome_cli/src/commands/ci.rs index d108a11a071..1d6a682bd42 100644 --- a/crates/rome_cli/src/commands/ci.rs +++ b/crates/rome_cli/src/commands/ci.rs @@ -11,17 +11,12 @@ pub(crate) fn ci(mut session: CliSession) -> Result<(), Termination> { let mut workspace_settings = WorkspaceSettings::default(); if let Some(configuration) = configuration { - workspace_settings.merge_with_configuration(configuration); + session + .app + .workspace + .update_settings(UpdateSettingsParams { configuration })?; } - apply_format_settings_from_cli(&mut session, &mut workspace_settings)?; - session - .app - .workspace - .update_settings(UpdateSettingsParams { - settings: workspace_settings, - })?; - execute_mode(Execution::new(TraversalMode::CI), session) } diff --git a/crates/rome_cli/src/commands/format.rs b/crates/rome_cli/src/commands/format.rs index 55ce62da781..616722d927f 100644 --- a/crates/rome_cli/src/commands/format.rs +++ b/crates/rome_cli/src/commands/format.rs @@ -11,7 +11,10 @@ pub(crate) fn format(mut session: CliSession) -> Result<(), Termination> { let mut workspace_settings = WorkspaceSettings::default(); if let Some(configuration) = configuration { - workspace_settings.merge_with_configuration(configuration); + session + .app + .workspace + .update_settings(UpdateSettingsParams { configuration })?; } apply_format_settings_from_cli(&mut session, &mut workspace_settings)?; @@ -57,13 +60,6 @@ pub(crate) fn format(mut session: CliSession) -> Result<(), Termination> { }) }; - session - .app - .workspace - .update_settings(UpdateSettingsParams { - settings: workspace_settings, - })?; - execute_mode(execution, session) } diff --git a/crates/rome_js_syntax/src/suppression.rs b/crates/rome_js_syntax/src/suppression.rs index 595a657aad7..23180b54d16 100644 --- a/crates/rome_js_syntax/src/suppression.rs +++ b/crates/rome_js_syntax/src/suppression.rs @@ -33,7 +33,7 @@ pub fn parse_suppression_comment(comment: &str) -> impl Iterator panic!("comment with unknown opening token {token:?}"), + token => panic!("comment with unknown opening token {token:?}, from {comment}"), }; comment.lines().filter_map(move |line| { diff --git a/crates/rome_lsp/src/config.rs b/crates/rome_lsp/src/config.rs index 934ddbc834b..e9b94190a28 100644 --- a/crates/rome_lsp/src/config.rs +++ b/crates/rome_lsp/src/config.rs @@ -1,5 +1,3 @@ -use rome_service::configuration::Configuration; -use rome_service::settings; use serde::{Deserialize, Serialize}; use serde_json::{Error, Value}; use tracing::trace; @@ -36,22 +34,4 @@ impl Config { ); Ok(()) } - - /// Convert the current configuration to an instance of [settings::WorkspaceSettings] - /// - /// If the configuration file is found we use it with its defaults, otherwise - /// we use the settings coming from the client - pub(crate) fn as_workspace_settings( - &self, - configuration: Option, - ) -> settings::WorkspaceSettings { - let mut settings = settings::WorkspaceSettings::default(); - - if let Some(configuration) = configuration { - trace!("Applying configuration coming from the configuration file"); - settings.merge_with_configuration(configuration); - } - - settings - } } diff --git a/crates/rome_lsp/src/session.rs b/crates/rome_lsp/src/session.rs index f666d5d1924..3bd69cdfef4 100644 --- a/crates/rome_lsp/src/session.rs +++ b/crates/rome_lsp/src/session.rs @@ -182,18 +182,19 @@ impl Session { }) .ok()?; let mut configuration = self.configuration.write().unwrap(); + // This operation is intended, we want to consume the configuration because once it's read // from the LSP, it's not needed anymore - let settings = config.as_workspace_settings(configuration.take()); - - trace!( - "The LSP will now use the following configuration: \n {:?}", - &settings - ); - - self.workspace - .update_settings(UpdateSettingsParams { settings }) - .ok()?; + if let Some(configuration) = configuration.take() { + trace!( + "The LSP will now use the following configuration: \n {:?}", + &configuration + ); + + self.workspace + .update_settings(UpdateSettingsParams { configuration }) + .ok()?; + } Some(()) }); diff --git a/crates/rome_service/src/configuration/javascript.rs b/crates/rome_service/src/configuration/javascript.rs index c3b8d937b91..0fcecf0d344 100644 --- a/crates/rome_service/src/configuration/javascript.rs +++ b/crates/rome_service/src/configuration/javascript.rs @@ -16,64 +16,74 @@ pub struct JavascriptConfiguration { /// /// If defined here, they should not emit diagnostics. #[serde( - skip_serializing_if = "IndexSet::is_empty", + skip_serializing_if = "Option::is_none", deserialize_with = "deserialize_globals", serialize_with = "serialize_globals" )] - pub globals: IndexSet, + pub globals: Option>, } -pub(crate) fn deserialize_globals<'de, D>(deserializer: D) -> Result, D::Error> +pub(crate) fn deserialize_globals<'de, D>( + deserializer: D, +) -> Result>, D::Error> where D: serde::de::Deserializer<'de>, { - deserializer.deserialize_seq(IndexVisitor::new()) -} - -struct IndexVisitor { - marker: PhantomData IndexSet>, -} + struct IndexVisitor { + marker: PhantomData Option>>, + } -impl IndexVisitor { - fn new() -> Self { - IndexVisitor { - marker: PhantomData, + impl IndexVisitor { + fn new() -> Self { + IndexVisitor { + marker: PhantomData, + } } } -} -impl<'de> Visitor<'de> for IndexVisitor { - type Value = IndexSet; + impl<'de> Visitor<'de> for IndexVisitor { + type Value = Option>; - // Format a message stating what data this Visitor expects to receive. - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("expecting a sequence") - } + // Format a message stating what data this Visitor expects to receive. + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("expecting a sequence") + } - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - let mut index_set = IndexSet::with_capacity(seq.size_hint().unwrap_or(0)); + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut index_set = IndexSet::with_capacity(seq.size_hint().unwrap_or(0)); - while let Some(value) = seq.next_element()? { - index_set.insert(value); - } + while let Some(value) = seq.next_element()? { + index_set.insert(value); + } - Ok(index_set) + Ok(Some(index_set)) + } } + + deserializer.deserialize_seq(IndexVisitor::new()) } -pub(crate) fn serialize_globals(globals: &IndexSet, s: S) -> Result +pub(crate) fn serialize_globals( + globals: &Option>, + s: S, +) -> Result where S: serde::ser::Serializer, { - let mut sequence = s.serialize_seq(Some(globals.len()))?; - let iter = globals.into_iter(); - for global in iter { - sequence.serialize_element(global)?; + if let Some(globals) = globals { + let mut sequence = s.serialize_seq(Some(globals.len()))?; + let iter = globals.into_iter(); + for global in iter { + sequence.serialize_element(global)?; + } + + sequence.end() + } else { + s.serialize_none() } - sequence.end() } #[derive(Default, Debug, Deserialize, Serialize, Eq, PartialEq)] diff --git a/crates/rome_service/src/settings.rs b/crates/rome_service/src/settings.rs index ba82fbaa2d8..ddabd3db587 100644 --- a/crates/rome_service/src/settings.rs +++ b/crates/rome_service/src/settings.rs @@ -42,10 +42,8 @@ impl WorkspaceSettings { self.linter = LinterSettings::from(linter) } - let globals = configuration.javascript.map(|j| j.globals); - if let Some(globals) = globals { - self.languages.javascript.globals = globals; - } + let globals = configuration.javascript.and_then(|j| j.globals); + self.languages.javascript.globals = globals; } /// It retrieves the severity based on the `code` of the rule and the current configuration. @@ -168,7 +166,7 @@ pub struct LanguageSettings { deserialize_with = "crate::configuration::deserialize_globals", serialize_with = "crate::configuration::serialize_globals" )] - pub globals: IndexSet, + pub globals: Option>, } /// Handle object holding a temporary lock on the workspace settings until diff --git a/crates/rome_service/src/workspace.rs b/crates/rome_service/src/workspace.rs index 759be1af8f1..5aae26795f6 100644 --- a/crates/rome_service/src/workspace.rs +++ b/crates/rome_service/src/workspace.rs @@ -51,8 +51,7 @@ //! document does not implement the required capability: for instance trying to //! format a file with a language that does not have a formatter -use crate::settings::WorkspaceSettings; -use crate::RomeError; +use crate::{Configuration, RomeError}; use rome_analyze::ActionCategory; pub use rome_analyze::RuleCategories; use rome_diagnostics::{CodeSuggestion, Diagnostic}; @@ -86,7 +85,7 @@ pub enum FeatureName { #[derive(serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct UpdateSettingsParams { - pub settings: WorkspaceSettings, + pub configuration: Configuration, } #[derive(serde::Serialize, serde::Deserialize)] diff --git a/crates/rome_service/src/workspace/server.rs b/crates/rome_service/src/workspace/server.rs index bd180403ddb..885bc310aa5 100644 --- a/crates/rome_service/src/workspace/server.rs +++ b/crates/rome_service/src/workspace/server.rs @@ -154,7 +154,7 @@ impl Workspace for WorkspaceServer { /// by another thread having previously panicked while holding the lock fn update_settings(&self, params: UpdateSettingsParams) -> Result<(), RomeError> { let mut settings = self.settings.write().unwrap(); - *settings = params.settings; + settings.merge_with_configuration(params.configuration); Ok(()) } diff --git a/crates/rome_service/src/workspace_types.rs b/crates/rome_service/src/workspace_types.rs index b19923e2ecc..800ebe655ae 100644 --- a/crates/rome_service/src/workspace_types.rs +++ b/crates/rome_service/src/workspace_types.rs @@ -15,7 +15,7 @@ use rome_js_factory::{ make, syntax::{JsAnyObjectMemberName, TsAnyName, TsAnyTypeMember, TsType, T}, }; -use rome_rowan::AstSeparatedList; +use rome_rowan::{AstSeparatedList, TriviaPieceKind}; /// Manages a queue of type definitions that need to be generated #[derive(Default)] @@ -58,12 +58,22 @@ fn instance_type<'a>( TsType::from(make::ts_object_type( make::token(T!['{']), make::ts_type_member_list(object.properties.iter().map(|(property, schema)| { - let (ts_type, optional) = schema_type(queue, root_schema, schema); + let (ts_type, optional, description) = schema_type(queue, root_schema, schema); assert!(!optional, "optional nested types are not supported"); + let mut property = make::ident(property); + if let Some(description) = description { + let comment = format!("/**\n\t* {} \n\t */", description); + let trivia = vec![ + (TriviaPieceKind::MultiLineComment, comment.as_str()), + (TriviaPieceKind::Newline, "\n"), + ]; + property = property.with_leading_trivia(trivia); + } + TsAnyTypeMember::from( make::ts_property_signature_type_member(JsAnyObjectMemberName::from( - make::js_literal_member_name(make::ident(property)), + make::js_literal_member_name(property), )) .with_type_annotation(make::ts_type_annotation(make::token(T![:]), ts_type)) .build(), @@ -78,7 +88,7 @@ fn instance_type<'a>( let items = array.items.as_ref().unwrap(); match items { SingleOrVec::Single(schema) => { - let (ts_type, optional) = schema_type(queue, root_schema, schema); + let (ts_type, optional, _) = schema_type(queue, root_schema, schema); assert!(!optional, "optional nested types are not supported"); TsType::from(make::ts_array_type( @@ -144,9 +154,13 @@ fn schema_object_type<'a>( queue: &mut ModuleQueue<'a>, root_schema: &'a RootSchema, schema: &'a SchemaObject, -) -> (TsType, bool) { +) -> (TsType, bool, Option<&'a String>) { // Start by detecting enum types by inspecting the `enum_values` field, i // the field is set return a union type generated from the literal enum values + let description = schema + .metadata + .as_ref() + .and_then(|s| s.description.as_ref()); let ts_type = schema .enum_values .as_deref() @@ -193,7 +207,7 @@ fn schema_object_type<'a>( TsType::from( make::ts_intersection_type(make::ts_intersection_type_element_list( all_of.iter().map(|ty| { - let (ts_type, optional) = schema_type(queue, root_schema, ty); + let (ts_type, optional, _) = schema_type(queue, root_schema, ty); assert!(!optional, "optional nested types are not supported"); ts_type }), @@ -211,7 +225,7 @@ fn schema_object_type<'a>( .or(subschemas.one_of.as_deref())?; Some(make_union_type(any_of.iter().map(|ty| { - let (ts_type, optional) = schema_type(queue, root_schema, ty); + let (ts_type, optional, _) = schema_type(queue, root_schema, ty); assert!(!optional, "optional nested types are not supported"); ts_type }))) @@ -228,7 +242,7 @@ fn schema_object_type<'a>( .map(|metadata| metadata.default.is_some()) .unwrap_or(false); - (ts_type, is_nullable || has_defaults) + (ts_type, is_nullable || has_defaults, description) } /// Generate a [TsType] node from a [Schema], returning the generated type @@ -238,16 +252,21 @@ fn schema_type<'a>( queue: &mut ModuleQueue<'a>, root_schema: &'a RootSchema, schema: &'a Schema, -) -> (TsType, bool) { +) -> (TsType, bool, Option<&'a String>) { match schema { // Types defined as `true` in the schema always pass validation, // map them to the `any` type - Schema::Bool(true) => (TsType::from(make::ts_any_type(make::token(T![any]))), true), + Schema::Bool(true) => ( + TsType::from(make::ts_any_type(make::token(T![any]))), + true, + None, + ), // Types defined as `false` in the schema never pass validation, // map them to the `never` type Schema::Bool(false) => ( TsType::from(make::ts_never_type(make::token(T![never]))), false, + None, ), Schema::Object(schema_object) => schema_object_type(queue, root_schema, schema_object), } @@ -255,7 +274,7 @@ fn schema_type<'a>( /// Generate and emit all the types defined in `root_schema` into the `module` pub fn generate_type<'a>( - module: &mut Vec, + module: &mut Vec<(JsAnyDeclaration, Option<&'a String>)>, queue: &mut ModuleQueue<'a>, root_schema: &'a RootSchema, ) -> TsType { @@ -298,13 +317,22 @@ pub fn generate_type<'a>( // property of the corresponding schema object let object = schema.object.as_deref().unwrap(); for (property, schema) in &object.properties { - let (ts_type, optional) = schema_type(queue, root_schema, schema); + let (ts_type, optional, description) = schema_type(queue, root_schema, schema); + + let mut property = make::ident(property); + if let Some(description) = description { + let comment = format!("/**\n\t* {} \n\t */", description); + let trivia = vec![ + (TriviaPieceKind::MultiLineComment, comment.as_str()), + (TriviaPieceKind::Newline, "\n"), + ]; + property = property.with_leading_trivia(trivia); + } - let mut builder = - make::ts_property_signature_type_member(JsAnyObjectMemberName::from( - make::js_literal_member_name(make::ident(property)), - )) - .with_type_annotation(make::ts_type_annotation(make::token(T![:]), ts_type)); + let mut builder = make::ts_property_signature_type_member( + JsAnyObjectMemberName::from(make::js_literal_member_name(property)), + ) + .with_type_annotation(make::ts_type_annotation(make::token(T![:]), ts_type)); if optional { builder = builder.with_optional_token(make::token(T![?])); @@ -313,7 +341,11 @@ pub fn generate_type<'a>( members.push(TsAnyTypeMember::from(builder.build())); } - module.push(JsAnyDeclaration::from( + let description = schema + .metadata + .as_ref() + .and_then(|s| s.description.as_ref()); + let current_module = JsAnyDeclaration::from( make::ts_interface_declaration( make::token(T![interface]), make::ts_identifier_binding(make::ident(name)), @@ -322,13 +354,14 @@ pub fn generate_type<'a>( make::token(T!['}']), ) .build(), - )); + ); + module.push((current_module, description)); } else { // If the schema for this type is not an object, emit it as a type alias - let (ts_type, optional) = schema_object_type(queue, root_schema, schema); + let (ts_type, optional, description) = schema_object_type(queue, root_schema, schema); assert!(!optional, "optional nested types are not supported"); - module.push(JsAnyDeclaration::from( + let current_module = JsAnyDeclaration::from( make::ts_type_alias_declaration( make::token(T![type]), make::ts_identifier_binding(make::ident(name)), @@ -336,7 +369,8 @@ pub fn generate_type<'a>( ts_type, ) .build(), - )); + ); + module.push((current_module, description)); } } diff --git a/crates/rome_wasm/build.rs b/crates/rome_wasm/build.rs index 80b3d4a5da6..1f5ebedec0b 100644 --- a/crates/rome_wasm/build.rs +++ b/crates/rome_wasm/build.rs @@ -23,7 +23,7 @@ fn main() -> io::Result<()> { let module = make::js_module( make::js_directive_list(None), - make::js_module_item_list(items.into_iter().map(|decl| { + make::js_module_item_list(items.into_iter().map(|(decl, _)| { JsAnyModuleItem::JsAnyStatement(match decl { JsAnyDeclaration::JsClassDeclaration(decl) => { JsAnyStatement::JsClassDeclaration(decl) diff --git a/editors/vscode/configuration_schema.json b/editors/vscode/configuration_schema.json index 0574ce88af5..6028e1d6d84 100644 --- a/editors/vscode/configuration_schema.json +++ b/editors/vscode/configuration_schema.json @@ -95,7 +95,10 @@ }, "globals": { "description": "A list of global bindings that should be ignored by the analyzers\n\nIf defined here, they should not emit diagnostics.", - "type": "array", + "type": [ + "array", + "null" + ], "items": { "type": "string" }, diff --git a/npm/backend-jsonrpc/src/workspace.ts b/npm/backend-jsonrpc/src/workspace.ts index 9e92920bd62..bf6a83d46a4 100644 --- a/npm/backend-jsonrpc/src/workspace.ts +++ b/npm/backend-jsonrpc/src/workspace.ts @@ -10,61 +10,116 @@ export interface RomePath { path: string; } export interface UpdateSettingsParams { - settings: WorkspaceSettings; -} -export interface WorkspaceSettings { - format?: FormatSettings; - languages?: LanguagesSettings; - linter?: LinterSettings; -} -export interface FormatSettings { - enabled: boolean; - format_with_errors: boolean; - indent_style?: IndentStyle; - line_width?: LineWidth; -} -export interface LanguagesSettings { - javascript?: LanguageSettings_for_JsLanguage; + configuration: Configuration; +} +/** + * The configuration that is contained inside the file `rome.json` + */ +export interface Configuration { + /** + * The configuration of the formatter + */ + formatter?: FormatterConfiguration; + /** + * Specific configuration for the JavaScript language + */ + javascript?: JavascriptConfiguration; + /** + * The configuration for the linter + */ + linter?: LinterConfiguration; +} +export interface FormatterConfiguration { + enabled?: boolean; + /** + * Stores whether formatting should be allowed to proceed if a given file has syntax errors + */ + formatWithErrors?: boolean; + /** + * The size of the indentation, 2 by default + */ + indentSize?: number; + /** + * The indent style. + */ + indentStyle?: PlainIndentStyle; + /** + * What's the max width of a line. Defaults to 80. + */ + lineWidth?: LineWidth; +} +export interface JavascriptConfiguration { + formatter?: JavascriptFormatter; + /** + * A list of global bindings that should be ignored by the analyzers + +If defined here, they should not emit diagnostics. + */ + globals?: string[]; } -export interface LinterSettings { - enabled: boolean; +export interface LinterConfiguration { + /** + * if `false`, it disables the feature and the linter won't be executed. `true` by default + */ + enabled?: boolean; + /** + * List of rules + */ rules?: Rules; } -export type IndentStyle = "Tab" | { Space: number }; +export type PlainIndentStyle = "tab" | "space"; +/** + * Validated value for the `line_width` formatter options + +The allowed range of values is 1..=320 + */ export type LineWidth = number; -export interface LanguageSettings_for_JsLanguage { - format?: JsFormatSettings; - globals?: string[]; - linter?: JsLinterSettings; +export interface JavascriptFormatter { + /** + * When properties in objects are quoted. Defaults to asNeeded. + */ + quoteProperties?: QuoteProperties; + /** + * The style for quotes. Defaults to double. + */ + quoteStyle?: QuoteStyle; } export interface Rules { js?: Js; jsx?: Jsx; + /** + * It enables the lint rules recommended by Rome. `true` by default. + */ recommended?: boolean; regex?: Regex; ts?: Ts; } -export interface JsFormatSettings { - quote_properties?: QuoteProperties; - quote_style?: QuoteStyle; -} -export interface JsLinterSettings { - globals: string[]; -} +export type QuoteProperties = "asNeeded" | "preserve"; +export type QuoteStyle = "double" | "single"; export interface Js { + /** + * It enables the recommended rules for this group + */ recommended?: boolean; } export interface Jsx { + /** + * It enables the recommended rules for this group + */ recommended?: boolean; } export interface Regex { + /** + * It enables the recommended rules for this group + */ recommended?: boolean; } export interface Ts { + /** + * It enables the recommended rules for this group + */ recommended?: boolean; } -export type QuoteProperties = "AsNeeded" | "Preserve"; -export type QuoteStyle = "Double" | "Single"; export interface OpenFileParams { content: string; path: RomePath; @@ -101,6 +156,9 @@ export type RuleCategory = "Syntax" | "Lint" | "Action"; export interface PullDiagnosticsResult { diagnostics: Diagnostic[]; } +/** + * A diagnostic message that can give information like errors or warnings. + */ export interface Diagnostic { children: SubDiagnostic[]; code?: string; @@ -114,30 +172,53 @@ export interface Diagnostic { tag?: DiagnosticTag; title: MarkupBuf; } +/** + * Everything that can be added to a diagnostic, like a suggestion that will be displayed under the actual error. + */ export interface SubDiagnostic { msg: MarkupBuf; severity: Severity; span: FileSpan; } +/** + * A note or help that is displayed under the diagnostic. + */ export interface Footer { msg: MarkupBuf; severity: Severity; } +/** + * A severity level for diagnostic messages. + +These are ordered in the following way: + */ export type Severity = "Help" | "Note" | "Warning" | "Error" | "Bug"; +/** + * A Suggestion that is provided by rslint, and can be reported to the user, and can be automatically applied if it has the right [`Applicability`]. + */ export interface CodeSuggestion { applicability: Applicability; labels: TextRangeSchema[]; msg: MarkupBuf; span: FileSpan; style: SuggestionStyle; + /** + * If the `FileId` is `None`, it's in the same file as his parent. + */ substitution: SuggestionChange; } export type DiagnosticTag = "Unnecessary" | "Deprecated" | "Both"; export type MarkupBuf = MarkupNodeBuf[]; +/** + * A range that is indexed in a specific file. + */ export interface FileSpan { file: number; range: TextRangeSchema; } +/** + * Indicates how a tool should manage this suggestion. + */ export type Applicability = | "Always" | "MaybeIncorrect" @@ -153,10 +234,21 @@ export interface MarkupNodeBuf { content: string; elements: MarkupElement[]; } +/** + * `InsertDelete` -- a single "atomic" change to text + +Must not overlap with other `InDel`s + */ export interface Indel { + /** + * Refers to offsets in the original text + */ delete: TextRangeSchema; insert: string; } +/** + * Enumeration of all the supported markup elements + */ export type MarkupElement = | "Emphasis" | "Dim" @@ -189,8 +281,17 @@ export interface Printed { sourcemap: SourceMarker[]; verbatim_ranges: TextRangeSchema[]; } +/** + * Lightweight sourcemap marker between source and output tokens + */ export interface SourceMarker { + /** + * Position of the marker in the output code + */ dest: number; + /** + * Position of the marker in the original source + */ source: number; } export interface FormatRangeParams { @@ -205,14 +306,32 @@ export interface FixFileParams { fix_file_mode: FixFileMode; path: RomePath; } +/** + * Which fixes should be applied during the analyzing phase + */ export type FixFileMode = "SafeFixes" | "SafeAndSuggestedFixes"; export interface FixFileResult { + /** + * List of all the code actions applied to the file + */ actions: FixAction[]; + /** + * New source code for the file with all fixes applied + */ code: string; + /** + * number of skipped suggested fixes + */ skipped_suggested_fixes: number; } export interface FixAction { + /** + * Source range at which this action was applied + */ range: TextRangeSchema; + /** + * Name of the rule that emitted this code action + */ rule_name: string; } export interface RenameParams { @@ -221,7 +340,13 @@ export interface RenameParams { symbol_at: number; } export interface RenameResult { + /** + * List of text edit operations to apply on the source code + */ indels: Indel[]; + /** + * Range of source code modified by this rename operation + */ range: TextRangeSchema; } export interface Workspace { diff --git a/npm/rome/package.json b/npm/rome/package.json index 4643cd1fa61..1303e28d547 100644 --- a/npm/rome/package.json +++ b/npm/rome/package.json @@ -42,7 +42,6 @@ "vitest": "^0.22.0", "vite": "^3.0.8", "@rometools/wasm-nodejs": "../wasm-nodejs", - "@rometools/backend-jsonrpc": "../backend-jsonrpc", - "wasm-pack": "^0.10.3" + "@rometools/backend-jsonrpc": "../backend-jsonrpc" } } \ No newline at end of file diff --git a/npm/rome/pnpm-lock.yaml b/npm/rome/pnpm-lock.yaml index d6b8891df6c..4ec7d132c83 100644 --- a/npm/rome/pnpm-lock.yaml +++ b/npm/rome/pnpm-lock.yaml @@ -6,7 +6,6 @@ specifiers: typescript: ^4.7.4 vite: ^3.0.8 vitest: ^0.22.0 - wasm-pack: ^0.10.3 devDependencies: '@rometools/backend-jsonrpc': link:../backend-jsonrpc @@ -14,7 +13,6 @@ devDependencies: typescript: 4.7.4 vite: 3.0.8 vitest: 0.22.0 - wasm-pack: 0.10.3 packages: @@ -45,36 +43,6 @@ packages: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} dev: true - /axios/0.21.4: - resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} - dependencies: - follow-redirects: 1.15.1 - transitivePeerDependencies: - - debug - dev: true - - /balanced-match/1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true - - /binary-install/0.1.1: - resolution: {integrity: sha512-DqED0D/6LrS+BHDkKn34vhRqOGjy5gTMgvYZsGK2TpNbdPuz4h+MRlNgGv5QBRd7pWq/jylM4eKNCizgAq3kNQ==} - engines: {node: '>=10'} - dependencies: - axios: 0.21.4 - rimraf: 3.0.2 - tar: 6.1.11 - transitivePeerDependencies: - - debug - dev: true - - /brace-expansion/1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - dev: true - /chai/4.3.6: resolution: {integrity: sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==} engines: {node: '>=4'} @@ -92,15 +60,6 @@ packages: resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==} dev: true - /chownr/2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} - dev: true - - /concat-map/0.0.1: - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} - dev: true - /debug/4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -329,27 +288,6 @@ packages: esbuild-windows-arm64: 0.14.54 dev: true - /follow-redirects/1.15.1: - resolution: {integrity: sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - dev: true - - /fs-minipass/2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} - dependencies: - minipass: 3.3.4 - dev: true - - /fs.realpath/1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - dev: true - /fsevents/2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -366,17 +304,6 @@ packages: resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==} dev: true - /glob/7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: true - /has/1.0.3: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} engines: {node: '>= 0.4.0'} @@ -384,17 +311,6 @@ packages: function-bind: 1.1.1 dev: true - /inflight/1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - dev: true - - /inherits/2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: true - /is-core-module/2.10.0: resolution: {integrity: sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==} dependencies: @@ -412,33 +328,6 @@ packages: get-func-name: 2.0.0 dev: true - /minimatch/3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - dependencies: - brace-expansion: 1.1.11 - dev: true - - /minipass/3.3.4: - resolution: {integrity: sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==} - engines: {node: '>=8'} - dependencies: - yallist: 4.0.0 - dev: true - - /minizlib/2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} - dependencies: - minipass: 3.3.4 - yallist: 4.0.0 - dev: true - - /mkdirp/1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - dev: true - /ms/2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} dev: true @@ -449,17 +338,6 @@ packages: hasBin: true dev: true - /once/1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - dependencies: - wrappy: 1.0.2 - dev: true - - /path-is-absolute/1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - dev: true - /path-parse/1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true @@ -490,13 +368,6 @@ packages: supports-preserve-symlinks-flag: 1.0.0 dev: true - /rimraf/3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - hasBin: true - dependencies: - glob: 7.2.3 - dev: true - /rollup/2.77.3: resolution: {integrity: sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==} engines: {node: '>=10.0.0'} @@ -515,18 +386,6 @@ packages: engines: {node: '>= 0.4'} dev: true - /tar/6.1.11: - resolution: {integrity: sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==} - engines: {node: '>= 10'} - dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 3.3.4 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 - dev: true - /tinypool/0.2.4: resolution: {integrity: sha512-Vs3rhkUH6Qq1t5bqtb816oT+HeJTXfwt2cbPH17sWHIYKTotQIFPk3tf2fgqRrVyMDVOc1EnPgzIxfIulXVzwQ==} engines: {node: '>=14.0.0'} @@ -613,21 +472,3 @@ packages: - supports-color - terser dev: true - - /wasm-pack/0.10.3: - resolution: {integrity: sha512-dg1PPyp+QwWrhfHsgG12K/y5xzwfaAoK1yuVC/DUAuQsDy5JywWDuA7Y/ionGwQz+JBZVw8jknaKBnaxaJfwTA==} - hasBin: true - requiresBuild: true - dependencies: - binary-install: 0.1.1 - transitivePeerDependencies: - - debug - dev: true - - /wrappy/1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - dev: true - - /yallist/4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - dev: true diff --git a/npm/rome/src/daemon.ts b/npm/rome/src/daemon.ts index 1e4720f04ad..5fadd347189 100644 --- a/npm/rome/src/daemon.ts +++ b/npm/rome/src/daemon.ts @@ -34,3 +34,40 @@ export class Deamon { throw new Error("could not connect to the daemon"); } } + +interface ErrorFromDaemon { + code: number; + data: string; + message: string; +} + +/** + * Error generated when communicating with the daemon + */ +export class DaemonError extends Error { + /** + * The code of the error + */ + code: number; + /** + * A better representation of the error, which might contain the stack trace of the error. + * + * This is useful for debug purpose + */ + data?: any; + /** + * The reason why there's been an error + */ + message: string; + + private constructor({ code, data, message }: ErrorFromDaemon) { + super(); + this.code = code; + this.data = data; + this.message = message; + } + + static fromError(e: any): DaemonError { + return new DaemonError(e as ErrorFromDaemon); + } +} diff --git a/npm/rome/src/index.ts b/npm/rome/src/index.ts index bc407bdd6c5..3f4942e32c3 100644 --- a/npm/rome/src/index.ts +++ b/npm/rome/src/index.ts @@ -1,6 +1,10 @@ import { NodeWasm } from "./nodeWasm"; import { Deamon } from "./daemon"; -import { Diagnostic } from "@rometools/backend-jsonrpc"; +import { Diagnostic, Configuration } from "@rometools/backend-jsonrpc"; +import { createError } from "./utils"; + +// Re-export of some useful types for users +export type { Configuration }; export interface FormatFilesDebugOptions extends FormatFilesOptions { /** @@ -122,10 +126,12 @@ export type RomeCreate = }; export class Rome { - private backend: Backend; + private readonly backend: Backend; + private readonly kind: BackendKind; - private constructor(backend: Backend) { + private constructor(backend: Backend, backendKind = BackendKind.NODE) { this.backend = backend; + this.kind = backendKind; } /** @@ -134,20 +140,44 @@ export class Rome { * When using the Daemon, an optional path to the Rome binary can be provided. * This is useful for debugging/test purpose. * - * @param options + * @param backendOptions */ - public static async create(options: RomeCreate): Promise { - switch (options.backendKind) { - case BackendKind.DAEMON: { - let client = await Deamon.connectToDaemon(options.pathToBinary); - return new Rome(client); - } + public static async create(backendOptions?: RomeCreate): Promise { + if (backendOptions) { + switch (backendOptions.backendKind) { + case BackendKind.DAEMON: { + let client = await Deamon.connectToDaemon( + backendOptions.pathToBinary, + ); + return new Rome(client, backendOptions.backendKind); + } - case BackendKind.NODE: - default: { - let client = await NodeWasm.loadWebAssembly(); - return new Rome(client); + case BackendKind.NODE: + default: { + let client = await NodeWasm.loadWebAssembly(); + return new Rome(client); + } } + } else { + let client = await NodeWasm.loadWebAssembly(); + return new Rome(client); + } + } + + /** + * Allows to apply a custom configuration. + * + * If fails when the configuration is incorrect. + * + * @param configuration + */ + public async applyConfiguration(configuration: Configuration): Promise { + try { + await this.backend.workspace.updateSettings({ + configuration, + }); + } catch (e) { + throw createError(e, this.kind); } } diff --git a/npm/rome/src/nodeWasm.ts b/npm/rome/src/nodeWasm.ts index f0f2a5dbe5a..ed5ec099ff9 100644 --- a/npm/rome/src/nodeWasm.ts +++ b/npm/rome/src/nodeWasm.ts @@ -22,3 +22,23 @@ export class NodeWasm { return Promise.resolve(new Workspace()); } } + +/** + * The error generated when communicating with WebAssembly + */ +export class WasmError extends Error { + /** + * The stack trace of the error. + * + * It might be useful, but the first like of the stack trace contains the error + */ + public stackTrace: string; + private constructor(stackTrace: string) { + super(); + this.stackTrace = stackTrace; + } + + static fromError(e: any): WasmError { + return new WasmError(e as string); + } +} diff --git a/npm/rome/src/utils.ts b/npm/rome/src/utils.ts new file mode 100644 index 00000000000..de850fcdbd3 --- /dev/null +++ b/npm/rome/src/utils.ts @@ -0,0 +1,20 @@ +import { BackendKind } from "./index"; +import { DaemonError } from "./daemon"; +import { WasmError } from "./nodeWasm"; + +/** + * Creates an error based on backend kind + * + * @param e + * @param backendKind + */ +export function createError( + e: any, + backendKind: BackendKind, +): DaemonError | WasmError { + if (backendKind === BackendKind.NODE) { + return WasmError.fromError(e); + } else { + return DaemonError.fromError(e); + } +} diff --git a/npm/rome/tests/wasm/__snapshots__/formatContent.test.mjs.snap b/npm/rome/tests/wasm/__snapshots__/formatContent.test.mjs.snap new file mode 100644 index 00000000000..18a4f19249c --- /dev/null +++ b/npm/rome/tests/wasm/__snapshots__/formatContent.test.mjs.snap @@ -0,0 +1,39 @@ +// Vitest Snapshot v1 + +exports[`Rome WebAssembly formatContent > should not format and have diagnostics > syntax error 1`] = ` +[ + { + "children": [], + "code": "SyntaxError", + "code_link": null, + "file_id": 0, + "footers": [], + "primary": { + "msg": [ + { + "content": "", + "elements": [], + }, + ], + "severity": "Error", + "span": { + "file": 0, + "range": [ + 11, + 12, + ], + }, + }, + "severity": "Error", + "suggestions": [], + "summary": null, + "tag": null, + "title": [ + { + "content": "expected a name for the function in a function declaration, but found none", + "elements": [], + }, + ], + }, +] +`; diff --git a/npm/rome/tests/wasm/formatContent.test.mjs b/npm/rome/tests/wasm/formatContent.test.mjs new file mode 100644 index 00000000000..9f465449691 --- /dev/null +++ b/npm/rome/tests/wasm/formatContent.test.mjs @@ -0,0 +1,129 @@ +import { describe, expect, it } from "vitest"; +import { BackendKind, Rome } from "../../dist"; + +describe("Rome WebAssembly formatContent", () => { + it("should format content", async () => { + const rome = await Rome.create({ + backendKind: BackendKind.NODE, + }); + + let result = await rome.formatContent("function f () { }", { + filePath: "example.js", + }); + + expect(result.content).toEqual("function f() {}\n"); + expect(result.diagnostics).toEqual([]); + }); + + it("should not format and have diagnostics", async () => { + const rome = await Rome.create({ + backendKind: BackendKind.NODE, + }); + + let content = "function () { }"; + let result = await rome.formatContent(content, { + filePath: "example.js", + }); + + expect(result.content).toEqual(content); + expect(result.diagnostics).toHaveLength(1); + expect(result.diagnostics[0].title[0].content).toContain( + "expected a name for the function in a function declaration, but found none", + ); + expect(result.diagnostics).toMatchSnapshot("syntax error"); + }); + + it("should format content in debug mode", async () => { + const rome = await Rome.create({ + backendKind: BackendKind.NODE, + }); + + let result = await rome.formatContent("function f() {}", { + filePath: "example.js", + debug: true, + }); + + expect(result.content).toEqual("function f() {}\n"); + expect(result.diagnostics).toEqual([]); + expect(result.ir).toEqual( + '["function", " ", "f", group(["(", ")"]), " ", "{", "}", hard_line_break]', + ); + }); + + it("should not format content with range", async () => { + const rome = await Rome.create({ + backendKind: BackendKind.NODE, + }); + + let result = await rome.formatContent("let a ; function g () { }", { + filePath: "file.js", + range: [20, 25], + }); + + expect(result.content).toEqual("function g() {}"); + expect(result.diagnostics).toEqual([]); + }); + + it("should not format content with range in debug mode", async () => { + const rome = await Rome.create({ + backendKind: BackendKind.NODE, + }); + + let result = await rome.formatContent("let a ; function g () { }", { + filePath: "file.js", + range: [20, 25], + debug: true, + }); + + expect(result.content).toEqual("function g() {}"); + expect(result.diagnostics).toEqual([]); + expect(result.ir).toEqual( + `[ + "let", + " ", + group(["a"]), + ";", + hard_line_break, + "function", + " ", + "g", + group(["(", ")"]), + " ", + "{", + "}", + hard_line_break +]`, + ); + }); + + it("should format content with custom configuration (8 spaces, single quotes, preserve quotes)", async () => { + const rome = await Rome.create({ + backendKind: BackendKind.NODE, + }); + + let content = `function f() { return { "foo": 'bar' } }`; + let formatted = `function f() { + return { 'foo': 'bar' }; +} +`; + + await rome.applyConfiguration({ + formatter: { + indentStyle: "space", + indentSize: 8, + }, + javascript: { + formatter: { + quoteStyle: "single", + quoteProperties: "preserve", + }, + }, + }); + + let result = await rome.formatContent(content, { + filePath: "example.js", + }); + + expect(result.content).toEqual(formatted); + }); +}); diff --git a/npm/rome/tests/wasm/formatFiles.test.mjs b/npm/rome/tests/wasm/formatFiles.test.mjs new file mode 100644 index 00000000000..9629ece5eed --- /dev/null +++ b/npm/rome/tests/wasm/formatFiles.test.mjs @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; +import { BackendKind, Rome } from "../../dist"; + +describe("Rome WebAssembly formatFiles", () => { + it("should not format files", async () => { + const rome = await Rome.create({ + backendKind: BackendKind.NODE, + }); + + let result = await rome.formatFiles(["./path/to/file.js"]); + + expect(result.content).toEqual(""); + expect(result.diagnostics).toEqual([]); + }); + + it("should not format files in debug mode", async () => { + const rome = await Rome.create({ + backendKind: BackendKind.NODE, + }); + + let result = await rome.formatFiles(["./path/to/file.js"], { + debug: true, + }); + + expect(result.content).toEqual(""); + expect(result.diagnostics).toEqual([]); + expect(result.ir).toEqual(""); + }); +}); diff --git a/website/playground/package.json b/website/playground/package.json index 7bc81262c13..75385c243f6 100644 --- a/website/playground/package.json +++ b/website/playground/package.json @@ -9,7 +9,7 @@ "build:js": "tsc && vite build", "build:wasm": "wasm-pack build --out-dir ../../npm/wasm-web --target web --release --scope rometools ../../crates/rome_wasm", "build:wasm-dev": "wasm-pack build --out-dir ../../npm/wasm-web --target web --dev --scope rometools ../../crates/rome_wasm", - "format": "cargo rome-cli format --write ./src", + "format": "cargo rome-cli-dev format --write ./src", "format:rome": "rome format --write src .", "tsc": "tsc" }, diff --git a/website/playground/src/romeWorker.ts b/website/playground/src/romeWorker.ts index b32780101ce..1d311617970 100644 --- a/website/playground/src/romeWorker.ts +++ b/website/playground/src/romeWorker.ts @@ -84,27 +84,25 @@ self.addEventListener("message", async (e) => { } = playgroundState; workspace.updateSettings({ - settings: { - format: { + configuration: { + formatter: { enabled: true, - format_with_errors: true, - line_width: lineWidth, - indent_style: - indentStyle === IndentStyle.Tab ? "Tab" : { Space: indentWidth }, + formatWithErrors: true, + lineWidth: lineWidth, + indentStyle: indentStyle === IndentStyle.Tab ? "tab" : "space", + indentSize: indentWidth, }, linter: { enabled: true, }, - languages: { - javascript: { - format: { - quote_style: - quoteStyle === QuoteStyle.Double ? "Double" : "Single", - quote_properties: - quoteProperties === QuoteProperties.Preserve - ? "Preserve" - : "AsNeeded", - }, + javascript: { + formatter: { + quoteStyle: + quoteStyle === QuoteStyle.Double ? "double" : "single", + quoteProperties: + quoteProperties === QuoteProperties.Preserve + ? "preserve" + : "asNeeded", }, }, }, diff --git a/xtask/codegen/src/generate_bindings.rs b/xtask/codegen/src/generate_bindings.rs index 74195e274e1..b28c9f53c4a 100644 --- a/xtask/codegen/src/generate_bindings.rs +++ b/xtask/codegen/src/generate_bindings.rs @@ -188,9 +188,18 @@ pub(crate) fn generate_workspace_bindings(mode: Mode) -> Result<()> { .build(), )]; - items.extend(declarations.into_iter().map(|decl| { + items.extend(declarations.into_iter().map(|(decl, description)| { + let mut export = make::token(T![export]); + if let Some(description) = description { + let comment = format!("/**\n\t* {} \n\t */\n", description); + let trivia = vec![ + (TriviaPieceKind::MultiLineComment, comment.as_str()), + (TriviaPieceKind::Newline, "\n"), + ]; + export = export.with_leading_trivia(trivia); + } JsAnyModuleItem::JsExport(make::js_export( - make::token(T![export]), + export, JsAnyExportClause::JsAnyDeclarationClause(match decl { JsAnyDeclaration::JsClassDeclaration(decl) => { JsAnyDeclarationClause::JsClassDeclaration(decl)