Skip to content
This repository has been archived by the owner on Aug 31, 2023. It is now read-only.

Commit

Permalink
feat: transformations
Browse files Browse the repository at this point in the history
  • Loading branch information
ematipico committed Jul 13, 2023
1 parent ac69cfa commit a40e49f
Show file tree
Hide file tree
Showing 16 changed files with 573 additions and 20 deletions.
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions crates/rome_analyze/src/categories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
}

Expand All @@ -191,6 +194,7 @@ impl From<RuleCategory> for RuleCategories {
RuleCategory::Syntax => RuleCategories::SYNTAX,
RuleCategory::Lint => RuleCategories::LINT,
RuleCategory::Action => RuleCategories::ACTION,
RuleCategory::Transformation => RuleCategories::TRANSFORMATION,
}
}
}
Expand All @@ -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)
}
}
Expand Down
8 changes: 8 additions & 0 deletions crates/rome_analyze/src/rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,14 @@ pub trait Rule: RuleMeta + Sized {
None
}
}

/// Returns a mutation to apply to the code
fn transform(
_ctx: &RuleContext<Self>,
_state: &Self::State,
) -> Option<BatchMutation<RuleLanguage<Self>>> {
None
}
}

/// Diagnostic object returned by a single analysis rule
Expand Down
2 changes: 1 addition & 1 deletion crates/rome_analyze/src/services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub trait FromServices: Sized {
) -> Result<Self, MissingServicesDiagnostic>;
}

#[derive(Default)]
#[derive(Debug, Default)]
pub struct ServiceBag {
services: HashMap<TypeId, Box<dyn Any>>,
}
Expand Down
102 changes: 97 additions & 5 deletions crates/rome_analyze/src/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,28 @@ use std::vec::IntoIter;
pub trait AnalyzerSignal<L: Language> {
fn diagnostic(&self) -> Option<AnalyzerDiagnostic>;
fn actions(&self) -> AnalyzerActionIter<L>;
fn transformations(&self) -> AnalyzerTransformationIter<L>;
}

/// 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<D, A, L, T> {
pub struct DiagnosticSignal<D, A, L, T, Tr> {
diagnostic: D,
action: A,
transformation: Tr,
_diag: PhantomData<(L, T)>,
}

impl<L: Language, D, T> DiagnosticSignal<D, fn() -> Option<AnalyzerAction<L>>, L, T>
impl<L: Language, D, T>
DiagnosticSignal<
D,
fn() -> Option<AnalyzerAction<L>>,
L,
T,
fn() -> Option<AnalyzerTransformation<L>>,
>
where
D: Fn() -> T,
Error: From<T>,
Expand All @@ -41,29 +50,32 @@ where
Self {
diagnostic: factory,
action: || None,
transformation: || None,
_diag: PhantomData,
}
}
}

impl<L: Language, D, A, T> DiagnosticSignal<D, A, L, T> {
pub fn with_action<B>(self, factory: B) -> DiagnosticSignal<D, B, L, T>
impl<L: Language, D, A, T, Tr> DiagnosticSignal<D, A, L, T, Tr> {
pub fn with_action<B>(self, factory: B) -> DiagnosticSignal<D, B, L, T, Tr>
where
B: Fn() -> Option<AnalyzerAction<L>>,
{
DiagnosticSignal {
diagnostic: self.diagnostic,
action: factory,
transformation: self.transformation,
_diag: PhantomData,
}
}
}

impl<L: Language, D, A, T> AnalyzerSignal<L> for DiagnosticSignal<D, A, L, T>
impl<L: Language, D, A, T, Tr> AnalyzerSignal<L> for DiagnosticSignal<D, A, L, T, Tr>
where
D: Fn() -> T,
Error: From<T>,
A: Fn() -> Option<AnalyzerAction<L>>,
Tr: Fn() -> Option<AnalyzerTransformation<L>>,
{
fn diagnostic(&self) -> Option<AnalyzerDiagnostic> {
let diag = (self.diagnostic)();
Expand All @@ -78,6 +90,14 @@ where
AnalyzerActionIter::new(vec![])
}
}

fn transformations(&self) -> AnalyzerTransformationIter<L> {
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]
Expand Down Expand Up @@ -236,6 +256,53 @@ impl<L: Language> AnalyzerActionIter<L> {
}
}

pub struct AnalyzerTransformationIter<L: Language> {
analyzer_transformations: IntoIter<AnalyzerTransformation<L>>,
}

impl<L: Language> Default for AnalyzerTransformationIter<L> {
fn default() -> Self {
Self {
analyzer_transformations: vec![].into_iter(),
}
}
}

impl<L: Language> AnalyzerTransformationIter<L> {
pub fn new<I>(transformations: I) -> Self
where
I: IntoIterator<Item = AnalyzerTransformation<L>>,
I::IntoIter: ExactSizeIterator,
{
Self {
analyzer_transformations: transformations
.into_iter()
.collect::<Vec<AnalyzerTransformation<L>>>()
.into_iter(),
}
}
}

impl<L: Language> Iterator for AnalyzerTransformationIter<L> {
type Item = AnalyzerTransformation<L>;

fn next(&mut self) -> Option<Self::Item> {
self.analyzer_transformations.next()
}
}
impl<L: Language> FusedIterator for AnalyzerTransformationIter<L> {}

impl<L: Language> ExactSizeIterator for AnalyzerTransformationIter<L> {
fn len(&self) -> usize {
self.analyzer_transformations.len()
}
}

#[derive(Debug, Clone)]
pub struct AnalyzerTransformation<L: Language> {
pub mutation: BatchMutation<L>,
}

/// Analyzer-internal implementation of [AnalyzerSignal] for a specific [Rule](crate::registry::Rule)
pub(crate) struct RuleSignal<'phase, R: Rule> {
root: &'phase RuleRoot<R>,
Expand Down Expand Up @@ -337,4 +404,29 @@ where
AnalyzerActionIter::new(vec![])
}
}

fn transformations(&self) -> AnalyzerTransformationIter<RuleLanguage<R>> {
let globals = self.options.globals();
let options = self.options.rule_options::<R>().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![])
}
}
}
9 changes: 5 additions & 4 deletions crates/rome_js_formatter/src/utils/string_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
12 changes: 6 additions & 6 deletions crates/rome_js_syntax/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
22 changes: 22 additions & 0 deletions crates/rome_js_transform/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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" }
17 changes: 17 additions & 0 deletions crates/rome_js_transform/src/declare_transformation.rs
Original file line number Diff line number Diff line change
@@ -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) )*;
}
};
}
Loading

0 comments on commit a40e49f

Please sign in to comment.