From bad6ff4c8154c09ab82a74eb2fc4896ca57522eb Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Thu, 13 Jul 2023 20:42:04 +0100 Subject: [PATCH] feat: transformations (#4696) --- Cargo.lock | 14 + crates/rome_analyze/src/categories.rs | 8 + crates/rome_analyze/src/rule.rs | 8 + crates/rome_analyze/src/services.rs | 2 +- crates/rome_analyze/src/signals.rs | 102 ++++++- .../src/utils/string_utils.rs | 9 +- crates/rome_js_syntax/Cargo.toml | 12 +- crates/rome_js_transform/Cargo.toml | 22 ++ .../src/declare_transformation.rs | 17 ++ crates/rome_js_transform/src/lib.rs | 98 +++++++ crates/rome_js_transform/src/registry.rs | 29 ++ .../rome_js_transform/src/transformers/mod.rs | 1 + .../src/transformers/ts_enum.rs | 263 ++++++++++++++++++ crates/rome_parser/src/lib.rs | 2 +- crates/rome_rowan/src/file_source.rs | 4 +- npm/backend-jsonrpc/src/workspace.ts | 2 +- 16 files changed, 573 insertions(+), 20 deletions(-) create mode 100644 crates/rome_js_transform/Cargo.toml create mode 100644 crates/rome_js_transform/src/declare_transformation.rs create mode 100644 crates/rome_js_transform/src/lib.rs create mode 100644 crates/rome_js_transform/src/registry.rs create mode 100644 crates/rome_js_transform/src/transformers/mod.rs create mode 100644 crates/rome_js_transform/src/transformers/ts_enum.rs diff --git a/Cargo.lock b/Cargo.lock index f35bfa0d43f..e56ec35d295 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2012,6 +2012,20 @@ dependencies = [ "serde", ] +[[package]] +name = "rome_js_transform" +version = "0.1.0" +dependencies = [ + "lazy_static", + "rome_analyze", + "rome_diagnostics", + "rome_js_factory", + "rome_js_formatter", + "rome_js_parser", + "rome_js_syntax", + "rome_rowan", +] + [[package]] name = "rome_js_unicode_table" version = "0.0.1" diff --git a/crates/rome_analyze/src/categories.rs b/crates/rome_analyze/src/categories.rs index cfd1f964b38..269b8410ee2 100644 --- a/crates/rome_analyze/src/categories.rs +++ b/crates/rome_analyze/src/categories.rs @@ -18,6 +18,8 @@ pub enum RuleCategory { /// This rule detects refactoring opportunities and emits code action /// signals Action, + /// This rule detects transformations that should be applied to the code + Transformation, } /// Actions that suppress rules should start with this string @@ -170,6 +172,7 @@ bitflags! { const SYNTAX = 1 << RuleCategory::Syntax as u8; const LINT = 1 << RuleCategory::Lint as u8; const ACTION = 1 << RuleCategory::Action as u8; + const TRANSFORMATION = 1 << RuleCategory::Transformation as u8; } } @@ -191,6 +194,7 @@ impl From for RuleCategories { RuleCategory::Syntax => RuleCategories::SYNTAX, RuleCategory::Lint => RuleCategories::LINT, RuleCategory::Action => RuleCategories::ACTION, + RuleCategory::Transformation => RuleCategories::TRANSFORMATION, } } } @@ -215,6 +219,10 @@ impl serde::Serialize for RuleCategories { flags.push(RuleCategory::Action); } + if self.contains(Self::TRANSFORMATION) { + flags.push(RuleCategory::Transformation); + } + serializer.collect_seq(flags) } } diff --git a/crates/rome_analyze/src/rule.rs b/crates/rome_analyze/src/rule.rs index 1275cce81e5..d4e25b6d4dd 100644 --- a/crates/rome_analyze/src/rule.rs +++ b/crates/rome_analyze/src/rule.rs @@ -369,6 +369,14 @@ pub trait Rule: RuleMeta + Sized { None } } + + /// Returns a mutation to apply to the code + fn transform( + _ctx: &RuleContext, + _state: &Self::State, + ) -> Option>> { + None + } } /// Diagnostic object returned by a single analysis rule diff --git a/crates/rome_analyze/src/services.rs b/crates/rome_analyze/src/services.rs index afcd7ffff2c..a435a09d0b3 100644 --- a/crates/rome_analyze/src/services.rs +++ b/crates/rome_analyze/src/services.rs @@ -41,7 +41,7 @@ pub trait FromServices: Sized { ) -> Result; } -#[derive(Default)] +#[derive(Debug, Default)] pub struct ServiceBag { services: HashMap>, } diff --git a/crates/rome_analyze/src/signals.rs b/crates/rome_analyze/src/signals.rs index c4bcba546b5..b8ed933b898 100644 --- a/crates/rome_analyze/src/signals.rs +++ b/crates/rome_analyze/src/signals.rs @@ -20,19 +20,28 @@ use std::vec::IntoIter; pub trait AnalyzerSignal { fn diagnostic(&self) -> Option; fn actions(&self) -> AnalyzerActionIter; + fn transformations(&self) -> AnalyzerTransformationIter; } /// Simple implementation of [AnalyzerSignal] generating a [AnalyzerDiagnostic] /// from a provided factory function. Optionally, this signal can be configured /// to also emit a code action, by calling `.with_action` with a secondary /// factory function for said action. -pub struct DiagnosticSignal { +pub struct DiagnosticSignal { diagnostic: D, action: A, + transformation: Tr, _diag: PhantomData<(L, T)>, } -impl DiagnosticSignal Option>, L, T> +impl + DiagnosticSignal< + D, + fn() -> Option>, + L, + T, + fn() -> Option>, + > where D: Fn() -> T, Error: From, @@ -41,29 +50,32 @@ where Self { diagnostic: factory, action: || None, + transformation: || None, _diag: PhantomData, } } } -impl DiagnosticSignal { - pub fn with_action(self, factory: B) -> DiagnosticSignal +impl DiagnosticSignal { + pub fn with_action(self, factory: B) -> DiagnosticSignal where B: Fn() -> Option>, { DiagnosticSignal { diagnostic: self.diagnostic, action: factory, + transformation: self.transformation, _diag: PhantomData, } } } -impl AnalyzerSignal for DiagnosticSignal +impl AnalyzerSignal for DiagnosticSignal where D: Fn() -> T, Error: From, A: Fn() -> Option>, + Tr: Fn() -> Option>, { fn diagnostic(&self) -> Option { let diag = (self.diagnostic)(); @@ -78,6 +90,14 @@ where AnalyzerActionIter::new(vec![]) } } + + fn transformations(&self) -> AnalyzerTransformationIter { + if let Some(transformation) = (self.transformation)() { + AnalyzerTransformationIter::new([transformation]) + } else { + AnalyzerTransformationIter::new(vec![]) + } + } } /// Code Action object returned by the analyzer, generated from a [crate::RuleAction] @@ -236,6 +256,53 @@ impl AnalyzerActionIter { } } +pub struct AnalyzerTransformationIter { + analyzer_transformations: IntoIter>, +} + +impl Default for AnalyzerTransformationIter { + fn default() -> Self { + Self { + analyzer_transformations: vec![].into_iter(), + } + } +} + +impl AnalyzerTransformationIter { + pub fn new(transformations: I) -> Self + where + I: IntoIterator>, + I::IntoIter: ExactSizeIterator, + { + Self { + analyzer_transformations: transformations + .into_iter() + .collect::>>() + .into_iter(), + } + } +} + +impl Iterator for AnalyzerTransformationIter { + type Item = AnalyzerTransformation; + + fn next(&mut self) -> Option { + self.analyzer_transformations.next() + } +} +impl FusedIterator for AnalyzerTransformationIter {} + +impl ExactSizeIterator for AnalyzerTransformationIter { + fn len(&self) -> usize { + self.analyzer_transformations.len() + } +} + +#[derive(Debug, Clone)] +pub struct AnalyzerTransformation { + pub mutation: BatchMutation, +} + /// Analyzer-internal implementation of [AnalyzerSignal] for a specific [Rule](crate::registry::Rule) pub(crate) struct RuleSignal<'phase, R: Rule> { root: &'phase RuleRoot, @@ -337,4 +404,29 @@ where AnalyzerActionIter::new(vec![]) } } + + fn transformations(&self) -> AnalyzerTransformationIter> { + let globals = self.options.globals(); + let options = self.options.rule_options::().unwrap_or_default(); + let ctx = RuleContext::new( + &self.query_result, + self.root, + self.services, + &globals, + &self.options.file_path, + &options, + ) + .ok(); + if let Some(ctx) = ctx { + let mut transformations = Vec::new(); + let mutation = R::transform(&ctx, &self.state); + if let Some(mutation) = mutation { + let transformation = AnalyzerTransformation { mutation }; + transformations.push(transformation) + } + AnalyzerTransformationIter::new(transformations) + } else { + AnalyzerTransformationIter::new(vec![]) + } + } } diff --git a/crates/rome_js_formatter/src/utils/string_utils.rs b/crates/rome_js_formatter/src/utils/string_utils.rs index 23b1a9883db..bf8084bd7a0 100644 --- a/crates/rome_js_formatter/src/utils/string_utils.rs +++ b/crates/rome_js_formatter/src/utils/string_utils.rs @@ -37,10 +37,11 @@ impl<'token> FormatLiteralStringToken<'token> { pub fn clean_text(&self, options: &JsFormatOptions) -> CleanedStringLiteralText { let token = self.token(); - debug_assert!(matches!( - token.kind(), - JS_STRING_LITERAL | JSX_STRING_LITERAL - )); + debug_assert!( + matches!(token.kind(), JS_STRING_LITERAL | JSX_STRING_LITERAL), + "Found kind {:?}", + token.kind() + ); let chosen_quote_style = match token.kind() { JSX_STRING_LITERAL => options.jsx_quote_style(), diff --git a/crates/rome_js_syntax/Cargo.toml b/crates/rome_js_syntax/Cargo.toml index baa193d5efe..33d8d5a5add 100644 --- a/crates/rome_js_syntax/Cargo.toml +++ b/crates/rome_js_syntax/Cargo.toml @@ -9,14 +9,14 @@ repository.workspace = true version = "0.0.2" [dependencies] -rome_console = { workspace = true } -rome_diagnostics = { workspace = true } -rome_rowan = { workspace = true } -schemars = { workspace = true, optional = true } -serde = { workspace = true, features = ["derive"], optional = true } +rome_console = { version = "0.0.1", path = "../rome_console" } +rome_diagnostics = { version = "0.0.1", path = "../rome_diagnostics" } +rome_rowan = { version = "0.0.1", path = "../rome_rowan" } +schemars = { version = "0.8.10", optional = true } +serde = { version = "1.0.136", features = ["derive"], optional = true } [dev-dependencies] -rome_js_factory = { workspace = true } +rome_js_factory = { path = "../rome_js_factory" } [features] serde = ["dep:serde", "schemars", "rome_rowan/serde"] diff --git a/crates/rome_js_transform/Cargo.toml b/crates/rome_js_transform/Cargo.toml new file mode 100644 index 00000000000..73e57591bea --- /dev/null +++ b/crates/rome_js_transform/Cargo.toml @@ -0,0 +1,22 @@ +[package] +authors.workspace = true +edition.workspace = true +license.workspace = true +name = "rome_js_transform" +repository.workspace = true +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +lazy_static = { workspace = true } +rome_analyze = { workspace = true } +rome_diagnostics = { workspace = true } +rome_js_factory = { workspace = true } +rome_js_syntax = { workspace = true } +rome_rowan = { workspace = true } + + +[dev-dependencies] +rome_js_formatter = { path = "../rome_js_formatter" } +rome_js_parser = { path = "../rome_js_parser" } diff --git a/crates/rome_js_transform/src/declare_transformation.rs b/crates/rome_js_transform/src/declare_transformation.rs new file mode 100644 index 00000000000..140e4f600bd --- /dev/null +++ b/crates/rome_js_transform/src/declare_transformation.rs @@ -0,0 +1,17 @@ +#[macro_export] +macro_rules! declare_transformation { + ( $( #[doc = $doc:literal] )+ $vis:vis $id:ident { + version: $version:literal, + name: $name:tt, + $( $key:ident: $value:expr, )* + } ) => { + $( #[doc = $doc] )* + $vis enum $id {} + + impl ::rome_analyze::RuleMeta for $id { + type Group = $crate::registry::TransformationGroup; + const METADATA: ::rome_analyze::RuleMetadata = + ::rome_analyze::RuleMetadata::new($version, $name, concat!( $( $doc, "\n", )* )) $( .$key($value) )*; + } + }; +} diff --git a/crates/rome_js_transform/src/lib.rs b/crates/rome_js_transform/src/lib.rs new file mode 100644 index 00000000000..ab74e950046 --- /dev/null +++ b/crates/rome_js_transform/src/lib.rs @@ -0,0 +1,98 @@ +mod declare_transformation; +mod registry; +mod transformers; + +use crate::registry::visit_transformation_registry; +use rome_analyze::{ + AnalysisFilter, Analyzer, AnalyzerContext, AnalyzerOptions, AnalyzerSignal, ControlFlow, + InspectMatcher, LanguageRoot, MatchQueryParams, MetadataRegistry, RuleRegistry, +}; +use rome_diagnostics::Error; +use rome_js_syntax::{JsFileSource, JsLanguage}; +use rome_rowan::BatchMutation; +use std::convert::Infallible; + +/// Return the static [MetadataRegistry] for the JS analyzer rules +pub fn metadata() -> &'static MetadataRegistry { + lazy_static::lazy_static! { + static ref METADATA: MetadataRegistry = { + let mut metadata = MetadataRegistry::default(); + visit_transformation_registry(&mut metadata); + metadata + }; + } + + &METADATA +} + +/// Run the analyzer on the provided `root`: this process will use the given `filter` +/// to selectively restrict analysis to specific rules / a specific source range, +/// then call `emit_signal` when an analysis rule emits a diagnostic or action. +/// Additionally, this function takes a `inspect_matcher` function that can be +/// used to inspect the "query matches" emitted by the analyzer before they are +/// processed by the lint rules registry +pub fn analyze_with_inspect_matcher<'a, V, F, B>( + root: &LanguageRoot, + filter: AnalysisFilter, + inspect_matcher: V, + options: &'a AnalyzerOptions, + source_type: JsFileSource, + mut emit_signal: F, +) -> (Option, Vec) +where + V: FnMut(&MatchQueryParams) + 'a, + F: FnMut(&dyn AnalyzerSignal) -> ControlFlow + 'a, + B: 'a, +{ + let mut registry = RuleRegistry::builder(&filter, root); + visit_transformation_registry(&mut registry); + + let (registry, mut services, diagnostics, visitors) = registry.build(); + + // Bail if we can't parse a rule option + if !diagnostics.is_empty() { + return (None, diagnostics); + } + + let mut analyzer = Analyzer::new( + metadata(), + InspectMatcher::new(registry, inspect_matcher), + |_| -> Vec> { unreachable!() }, + |_| {}, + &mut emit_signal, + ); + + for ((phase, _), visitor) in visitors { + analyzer.add_visitor(phase, visitor); + } + + services.insert_service(source_type); + ( + analyzer.run(AnalyzerContext { + root: root.clone(), + range: filter.range, + services, + options, + }), + diagnostics, + ) +} + +/// Run the analyzer on the provided `root`: this process will use the given `filter` +/// to selectively restrict analysis to specific rules / a specific source range, +/// then call `emit_signal` when an analysis rule emits a diagnostic or action +pub fn transform<'a, F, B>( + root: &LanguageRoot, + filter: AnalysisFilter, + options: &'a AnalyzerOptions, + source_type: JsFileSource, + emit_signal: F, +) -> (Option, Vec) +where + F: FnMut(&dyn AnalyzerSignal) -> ControlFlow + 'a, + B: 'a, +{ + analyze_with_inspect_matcher(root, filter, |_| {}, options, source_type, emit_signal) +} + +pub(crate) type JsBatchMutation = BatchMutation; diff --git a/crates/rome_js_transform/src/registry.rs b/crates/rome_js_transform/src/registry.rs new file mode 100644 index 00000000000..596a2525dc2 --- /dev/null +++ b/crates/rome_js_transform/src/registry.rs @@ -0,0 +1,29 @@ +use crate::transformers::ts_enum::TsEnum; +use rome_analyze::{GroupCategory, RegistryVisitor, RuleCategory, RuleGroup}; +use rome_js_syntax::JsLanguage; + +pub(crate) struct TransformationGroup; +pub(crate) struct TransformationCategory; + +impl GroupCategory for TransformationCategory { + type Language = JsLanguage; + const CATEGORY: RuleCategory = RuleCategory::Transformation; + + fn record_groups + ?Sized>(registry: &mut V) { + registry.record_group::() + } +} + +impl RuleGroup for TransformationGroup { + type Language = JsLanguage; + type Category = TransformationCategory; + const NAME: &'static str = "transformations"; + + fn record_rules + ?Sized>(registry: &mut V) { + registry.record_rule::(); + } +} + +pub fn visit_transformation_registry>(registry: &mut V) { + registry.record_category::(); +} diff --git a/crates/rome_js_transform/src/transformers/mod.rs b/crates/rome_js_transform/src/transformers/mod.rs new file mode 100644 index 00000000000..5fd38ed1fd8 --- /dev/null +++ b/crates/rome_js_transform/src/transformers/mod.rs @@ -0,0 +1 @@ +pub(crate) mod ts_enum; diff --git a/crates/rome_js_transform/src/transformers/ts_enum.rs b/crates/rome_js_transform/src/transformers/ts_enum.rs new file mode 100644 index 00000000000..bbbcdedc1cb --- /dev/null +++ b/crates/rome_js_transform/src/transformers/ts_enum.rs @@ -0,0 +1,263 @@ +use crate::{declare_transformation, JsBatchMutation}; +use rome_analyze::context::RuleContext; +use rome_analyze::{Ast, Rule}; +use rome_js_factory::make::{ + ident, js_assignment_expression, js_call_argument_list, js_call_arguments, js_call_expression, + js_computed_member_assignment, js_decorator_list, js_directive_list, js_expression_statement, + js_formal_parameter, js_function_body, js_function_expression, js_identifier_assignment, + js_identifier_binding, js_identifier_expression, js_logical_expression, js_module_item_list, + js_number_literal_expression, js_object_expression, js_object_member_list, js_parameter_list, + js_parameters, js_parenthesized_expression, js_reference_identifier, js_statement_list, + js_string_literal, js_string_literal_expression, js_variable_declaration, + js_variable_declarator, js_variable_declarator_list, js_variable_statement, token, +}; +use rome_js_syntax::{ + AnyJsAssignment, AnyJsAssignmentPattern, AnyJsBinding, AnyJsBindingPattern, AnyJsCallArgument, + AnyJsExpression, AnyJsFormalParameter, AnyJsLiteralExpression, AnyJsModuleItem, AnyJsParameter, + AnyJsStatement, JsAssignmentExpression, JsComputedMemberAssignment, JsExpressionStatement, + JsFunctionExpression, JsLogicalExpression, JsModule, JsStatementList, JsVariableStatement, + TsEnumDeclaration, T, +}; +use rome_rowan::{AstNode, BatchMutationExt, TriviaPieceKind}; +use std::iter; + +declare_transformation! { + /// Transform a TypeScript [TsEnumDeclaration] + pub(crate) TsEnum { + version: "next", + name: "transformEnum", + recommended: false, + } +} + +pub struct TsEnumMembers { + name: String, + member_names: Vec, +} + +impl Rule for TsEnum { + type Query = Ast; + type State = TsEnumMembers; + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let node = ctx.query(); + let mut member_names = vec![]; + let id = node.id().ok()?; + let name = id.text(); + for member in node.members() { + let member = member.ok()?; + member_names.push(member.name().ok()?.text()) + } + + Some(TsEnumMembers { name, member_names }) + } + + fn transform(ctx: &RuleContext, state: &Self::State) -> Option { + let node = ctx.query(); + let mut mutation = node.clone().begin(); + let parent = node.syntax().grand_parent(); + + if let Some(parent) = parent { + if let Some(module) = JsModule::cast(parent) { + let variable = make_variable(state); + let function = make_function_caller(state); + let statements = vec![ + AnyJsModuleItem::AnyJsStatement(AnyJsStatement::JsVariableStatement(variable)), + AnyJsModuleItem::AnyJsStatement(AnyJsStatement::JsExpressionStatement( + function, + )), + ]; + let modules = js_module_item_list(statements.into_iter()); + let new_modules = module.clone().with_items(modules); + mutation.replace_element(module.into(), new_modules.into()); + } + } + + Some(mutation) + } +} + +/// Out of an enum, this functions emits the generation of the: +/// +/// ```ts +/// enum Foo {} +/// var Foo; +/// ``` +fn make_variable(node: &TsEnumMembers) -> JsVariableStatement { + let binding = js_variable_declarator(AnyJsBindingPattern::AnyJsBinding( + AnyJsBinding::JsIdentifierBinding(js_identifier_binding(ident(node.name.as_str()))), + )) + .build(); + + let list = js_variable_declarator_list(iter::once(binding), iter::empty()); + js_variable_statement(js_variable_declaration( + token(T![var]).with_trailing_trivia(iter::once((TriviaPieceKind::Whitespace, " "))), + list, + )) + .with_semicolon_token(token(T![;])) + .build() +} + +fn make_function_caller(node: &TsEnumMembers) -> JsExpressionStatement { + let callee = js_parenthesized_expression( + token(T!['(']), + AnyJsExpression::JsFunctionExpression(make_function(node)), + token(T![')']), + ); + let argument = AnyJsCallArgument::AnyJsExpression(AnyJsExpression::JsLogicalExpression( + make_logical_expression(node), + )); + let arguments = js_call_arguments( + token(T!['(']), + js_call_argument_list(iter::once(argument), iter::empty()), + token(T![')']), + ); + let expression = js_call_expression( + AnyJsExpression::JsParenthesizedExpression(callee), + arguments, + ) + .build(); + js_expression_statement(AnyJsExpression::JsCallExpression(expression)) + .with_semicolon_token(token(T![;])) + .build() +} + +fn make_function(node: &TsEnumMembers) -> JsFunctionExpression { + let parameters_list = js_parameter_list( + iter::once(AnyJsParameter::AnyJsFormalParameter( + AnyJsFormalParameter::JsFormalParameter( + js_formal_parameter( + js_decorator_list(vec![]), + AnyJsBindingPattern::AnyJsBinding(AnyJsBinding::JsIdentifierBinding( + js_identifier_binding(ident(node.name.as_str())), + )), + ) + .build(), + ), + )), + iter::empty(), + ); + let parameters = js_parameters(token(T!['(']), parameters_list, token(T![')'])); + + let body = js_function_body( + token(T!['{']), + js_directive_list(iter::empty()), + make_members(node), + token(T!['}']), + ); + js_function_expression(token(T![function]), parameters, body).build() +} + +fn make_members(ts_enum: &TsEnumMembers) -> JsStatementList { + let mut list = vec![]; + for name in &ts_enum.member_names { + list.push(AnyJsStatement::JsExpressionStatement( + make_high_order_assignment(ts_enum.name.as_str(), name.as_str(), "0"), + )); + } + + js_statement_list(list.into_iter()) +} + +fn make_logical_expression(node: &TsEnumMembers) -> JsLogicalExpression { + let left = js_identifier_expression(js_reference_identifier(ident(node.name.as_str()))); + + let expression = js_assignment_expression( + AnyJsAssignmentPattern::AnyJsAssignment(AnyJsAssignment::JsIdentifierAssignment( + js_identifier_assignment(ident(node.name.as_str())), + )), + token(T![=]), + AnyJsExpression::JsObjectExpression(js_object_expression( + token(T!['{']), + js_object_member_list(iter::empty(), iter::empty()), + token(T!['}']), + )), + ); + + let right = js_parenthesized_expression( + token(T!['(']), + AnyJsExpression::JsAssignmentExpression(expression), + token(T![')']), + ); + + js_logical_expression( + AnyJsExpression::JsIdentifierExpression(left), + token(T![||]), + AnyJsExpression::JsParenthesizedExpression(right), + ) +} + +fn make_high_order_assignment( + enum_name: &str, + member_name: &str, + member_value: &str, +) -> JsExpressionStatement { + let left = js_computed_member_assignment( + AnyJsExpression::JsIdentifierExpression(js_identifier_expression(js_reference_identifier( + ident(enum_name), + ))), + token(T!['[']), + AnyJsExpression::JsAssignmentExpression(make_assignment_expression_from_member( + enum_name, + member_name, + member_value, + )), + token(T![']']), + ); + let right = js_string_literal_expression(js_string_literal(member_name)); + + let expression = js_assignment_expression( + AnyJsAssignmentPattern::AnyJsAssignment(AnyJsAssignment::JsComputedMemberAssignment(left)), + token(T![=]), + AnyJsExpression::AnyJsLiteralExpression(AnyJsLiteralExpression::JsStringLiteralExpression( + right, + )), + ); + + js_expression_statement(AnyJsExpression::JsAssignmentExpression(expression)) + .with_semicolon_token(token(T![;])) + .build() +} + +/// Makes +/// ```js +/// Foo["Lorem"] = 0 +/// ``` +fn make_assignment_expression_from_member( + enum_name: &str, + member_name: &str, + member_value: &str, +) -> JsAssignmentExpression { + let left = make_computed_member_assignment(enum_name, member_name); + let right = js_number_literal_expression(ident(member_value)); + + js_assignment_expression( + AnyJsAssignmentPattern::AnyJsAssignment(AnyJsAssignment::JsComputedMemberAssignment(left)), + token(T![=]), + AnyJsExpression::AnyJsLiteralExpression(AnyJsLiteralExpression::JsNumberLiteralExpression( + right, + )), + ) +} + +/// Creates +/// ```js +/// Foo["Lorem"] +/// ``` +fn make_computed_member_assignment( + enum_name: &str, + member_name: &str, +) -> JsComputedMemberAssignment { + let object = js_identifier_expression(js_reference_identifier(ident(enum_name))); + let member = js_string_literal_expression(js_string_literal(member_name)); + js_computed_member_assignment( + AnyJsExpression::JsIdentifierExpression(object), + token(T!['[']), + AnyJsExpression::AnyJsLiteralExpression(AnyJsLiteralExpression::JsStringLiteralExpression( + member, + )), + token(T![']']), + ) +} diff --git a/crates/rome_parser/src/lib.rs b/crates/rome_parser/src/lib.rs index 3b0ce63887d..eada565f5a7 100644 --- a/crates/rome_parser/src/lib.rs +++ b/crates/rome_parser/src/lib.rs @@ -830,7 +830,7 @@ impl AnyParse { F: FileSource<'a, L> + 'static, L: Language + 'static, { - self.file_source.unwrap_cast(path) + self.file_source.unwrap_cast_from_path(path) } pub fn tree(&self) -> N diff --git a/crates/rome_rowan/src/file_source.rs b/crates/rome_rowan/src/file_source.rs index 6764f0f2f59..60b6dbbb8f6 100644 --- a/crates/rome_rowan/src/file_source.rs +++ b/crates/rome_rowan/src/file_source.rs @@ -76,7 +76,7 @@ impl AnyFileSource { /// The function will panic if: /// - the original type and given type mismatch /// - it's possible to retrieve the correct [FileSource] from the given [Path] - pub fn unwrap_cast<'a, F, L>(&self, path: &'a Path) -> Result + pub fn unwrap_cast_from_path<'a, F, L>(&self, path: &'a Path) -> Result where F: FileSource<'a, L> + 'static, L: Language + 'static, @@ -122,7 +122,7 @@ mod test { }; let send_first = first_test.as_any_file_source(); - let cast = send_first.unwrap_cast::(path.as_path()); + let cast = send_first.unwrap_cast_from_path::(path.as_path()); assert!(cast.is_ok()); diff --git a/npm/backend-jsonrpc/src/workspace.ts b/npm/backend-jsonrpc/src/workspace.ts index 07da99cac66..1dcf3cea1ad 100644 --- a/npm/backend-jsonrpc/src/workspace.ts +++ b/npm/backend-jsonrpc/src/workspace.ts @@ -1042,7 +1042,7 @@ export interface PullDiagnosticsParams { path: RomePath; } export type RuleCategories = RuleCategory[]; -export type RuleCategory = "Syntax" | "Lint" | "Action"; +export type RuleCategory = "Syntax" | "Lint" | "Action" | "Transformation"; export interface PullDiagnosticsResult { diagnostics: Diagnostic[]; errors: number;