Typescript
Hook for transforming code in TypeScript
schema-version: v3
kind: plugin
metadata:
name: code-transformation-plugin
display-name: code-transformation-plugin
description: Code transformation example.
version: 1.0.0
spec:
hooks:
- type: code-transformation
engine-version: 0.2.0-beta
trigger: after-render
language: typescript
trans-pattern-path: trans-patterns/pattern.ts
source-path: src/code.ts
Statement
The transformation engine supports constructs called statements. A statement is a language command that produces a side effect and does not result in a value. This is the most common type of construct in TypeScript. Here are some examples of statuses:
- import command
- export command
- variable declaration
- function declaration
- arrow function declaration
- generator declaration
- class declaration
- enum declaration
- interface declaration
- namespace declaration
- function call
- method call
- variable assignment
- decision commands (if and switch)
- iteration commands (for, while, and do-while)
- return, yield, break, and continue commands
- with command
- try command
In any valid part of the language where one of these statements may occur, you can use the transformation operators in the transformation pattern you are creating. For example:
<<<< patter-name: my-pattern-name; pattern-scope: file-scope; >>>>
<...>
import MyClass from './Myclass';
+<import MyClass2 from 'MyClass2';>+
<...>
function print_message(message:string):void{
+<if (message === null || message === undefined) {
throw new Error("The string is null or undefined");
}>+
console.log('MESSAGE: ' + message);
}
let message: string = 'My Message';
print_message(message);
<...>
-<message = 'My Message 2';>-
-<print_message(message);>-
<...>
const myClassInstance = new MyClass();
+<const myClass2Instance = new MyClass2();>+
+<class MyClass3 {
myMethod(): string {
return "My Method";
}
}>+
+<const myClass3Instance = new MyClass3();>+
+<print_message(myClass3Instance.myMethod());>+
<...>
In the previous transformation pattern example, several ellipsis operators are used to replace statements in parts of the code.
- In line 3, an operator inserts an import statement.
- From line 8 to line 10, an operator inserts an
if
statement in the body of a function. - In lines 19 and 20, each operator removes an assignment and function call statements, respectively.
- In line 25, an operator inserts a variable declaration statement.
- From line 27 to line 32, an operator inserts a class declaration.
- In line 34, an operator inserts a variable declaration.
- Finally, in line 35, an operator inserts a function call.
Class element
Class elements are constructs supported by the transformation engine. A class element is a declaration within the body of a class. See below some examples of class elements:
- method declaration
- declaration of ownership
- constructor declaration
In any valid part of the language where one of these class elements may occur, you can use the transformation operators in the transformation pattern you have created. Example:
<<<< patter-name: my-pattern-name; pattern-scope: file-scope; >>>>
<...>
class MyClass {
+<myprop:string = "";>+
<...>
myMethod1(): string {
<...>
}
<...>
+<myMethod2(): string {
return "Executed myMethod2";
}>+
}
<...>
In the previous transformation pattern example, you can observe several ellipsis operators used in parts of the code to replace class elements.
- In line 3, an operator inserts a property class element as the first element of the
MyClass
class. - From line 13 to line 15, an operator inserts a method declaration class element as the last element of the
MyClass
class.
Examples of advanced transformation patterns
To simplify creating your script using transformation patterns, duplicate the original file you want to transform and make the necessary edits with the desired patterns.
Examples of creating complex transformation patterns to transform source code are provided below. The examples demonstrate the files in their original state, the transformation pattern, and the original transformed file.
1. Example of ellipsis and insertion operators
- Source Code
- Transformation Pattern
- Transformed Code
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {}
<<<< patter-name: my-pattern-name; pattern-scope: file-scope; >>>>
+<import { CommonModule } from '@angular/common';>+
+<import { FormsModule } from '@angular/forms';>+
+<import { MyComponent } from './my-component.component';>+
+<import { MyService } from './my-service.service';>+
<...>
@NgModule({
<...>
declarations: [+<MyComponent>+ <...>],
imports: [+<CommonModule>+ +<FormsModule>+ <...>],
providers: [+<MyService>+ <...>]
<...>
})
export class AppModule {}
<...>
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MyComponent } from './my-component.component';
import { MyService } from './my-service.service';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
@NgModule({
declarations: [MyComponent, AppComponent],
imports: [CommonModule, FormsModule, BrowserModule],
providers: [MyService],
bootstrap: [AppComponent]
})
export class AppModule { }
2. Example of ellipsis and removal operators
- Source Code
- Transformation Pattern
- Transformed Code
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-my-component',
templateUrl: './my-component.component.html',
styleUrls: ['./my-component.component.css']
})
export class MyComponent implements OnInit {
public greeting: string = 'Hello World!';
public counter: number = 0;
constructor() {}
ngOnInit(): void {}
public incrementCounter(): void {
this.counter++;
}
}
<<<< patter-name: my-pattern-name; pattern-scope: file-scope; >>>>
<...>
@Component({<...>})
export class MyComponent implements OnInit {
<...>
-<public counter: number = 0;>-
<...>
-<public incrementCounter(): void {
this.counter++;
}>-
}
<...>
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-my-component',
templateUrl: './my-component.component.html',
styleUrls: ['./my-component.component.css']
})
export class MyComponent implements OnInit {
public greeting: string = 'Hello World!';
constructor() {}
ngOnInit(): void {}
}
3. Example with all ellipsis, insertion, and removal operators
- Source Code
- Transformation Pattern
- Transformed Code
import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';
@Component({
selector: 'app-example',
template: '<div>{{ data }}</div>',
styleUrls: ['./example.component.css']
})
export class ExampleComponent implements OnInit {
data: string;
constructor(private dataService: DataService) { }
ngOnInit(): void {
this.data = this.dataService.getData();
}
}
<<<< patter-name: my-pattern-name; pattern-scope: file-scope; >>>>
<...>
export class ExampleComponent implements OnInit {
<...>
data: string;
<...>
constructor(private dataService: DataService) {<...>}
<...>
ngOnInit(): void {
<...>
+<this.dataService.getDataAsync().subscribe(
(result: string) => {
this.data = result;
},
(error: any) => {
console.error('Error fetching data:', error);
}
);>+
-<this.data = this.dataService.getData();>-
<...>
}
<...>
}
<...>
import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';
@Component({
selector: 'app-example',
template: '<div>{{ data }}</div>',
styleUrls: ['./example.component.css']
})
export class ExampleComponent implements OnInit {
data: string;
constructor(private dataService: DataService) { }
ngOnInit(): void {
this.dataService.getDataAsync().subscribe(
(result: string) => {
this.data = result;
},
(error: any) => {
console.error('Error fetching data:', error);
}
);
}
}
4. Example with ID Match and String Match Operators
- Original Code
- Transformation Pattern
- Transformed Code
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { MyComponentA } from './my-component-a/my-component-a.component';
import { MyComponentB } from './my-component-b/my-component-b.component';
import { MyComponentC } from './my-component-c/my-component-c.component';
@NgModule({
declarations: [
AppComponent,
MyComponentA,
MyComponentB,
MyComponentC
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
<<<< pattern-name: remove-components; pattern-scope: snippet-scope >>>>
-<import { *<MyComponent.*>* } from *<" '.*' ">*;>-
<...>
-<*<MyComponent.*?,?>*>-
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }