Angular Code Action for Component Property Generation
This challenge focuses on creating a custom code action within an Angular development environment. You will implement a feature that allows developers to automatically generate a component property based on a selected template expression within an Angular template file. This is useful for quickly scaffolding component logic and improving developer productivity.
Problem Description
You are tasked with building a VS Code extension (or a similar IDE extension) that provides a context-aware code action for Angular developers. When a user selects an expression within an Angular template (.html) file that is likely to represent a component property (e.g., {{ someValue }} or [someInput]="someValue"), a code action should appear. Upon selecting this code action, the extension should generate a corresponding property declaration in the associated component's TypeScript file (.ts).
Key Requirements:
- Code Action Trigger: The code action should be triggered when the user right-clicks within an Angular template file and selects an appropriate option from the context menu.
- Expression Recognition: The code action should intelligently identify expressions that represent potential component properties. This includes:
- Interpolation:
{{ myProperty }} - Property Binding:
[myInput]="myProperty" - Event Binding:
(myEvent)="myHandler($event)"(wheremyHandleris the potential property)
- Interpolation:
- Property Generation:
- The code action should extract the
myProperty(ormyHandler) identifier from the selected expression. - It should then navigate to the associated
.tsfile for the component. - A new property declaration should be added to the component class.
- The generated property should have a default type (e.g.,
anyorstring) and a default value (e.g.,nullor''). The developer can then refine this.
- The code action should extract the
- File Association: The extension needs to be able to determine the corresponding
.tsfile for a given.htmltemplate. This typically follows a naming convention (e.g.,my-component.component.htmlandmy-component.component.ts). - Cursor Placement: After generation, the cursor should ideally be placed on the newly generated property name, allowing for immediate renaming or type modification.
Expected Behavior:
- When a user selects
{{ myData }}inmy-component.component.htmland triggers the code action, a new linemy-data: any = null;(or similar) should be added tomy-component.component.tswithin theMyComponentclass. - When a user selects
[user]="currentUser"inmy-component.component.htmland triggers the code action, a new lineuser: any = null;should be added tomy-component.component.ts. - When a user selects
(click)="onButtonClick()"and triggers the code action, a new lineonButtonClick: () => void = () => {};should be added tomy-component.component.ts.
Edge Cases:
- Multiple matches: If an identifier appears multiple times in the template, the action should generate the property in the
.tsfile regardless. - Existing property: If a property with the same name already exists in the
.tsfile, the code action should ideally not create a duplicate, perhaps by informing the user or doing nothing. For this challenge, you can assume it's acceptable to add it if it doesn't exist. - Invalid template syntax: The extension should handle cases where the selected text is not a valid Angular expression.
- Component not found: If the corresponding
.tsfile cannot be located.
Examples
Example 1: Interpolation
Input (in user-profile.component.html):
<div>
<h1>{{ userName }}</h1>
<p>Email: {{ userEmail }}</p>
</div>
Action: User selects userName inside {{ userName }}.
Output (in user-profile.component.ts):
import { Component } from '@angular/core';
@Component({
selector: 'app-user-profile',
templateUrl: './user-profile.component.html',
styleUrls: ['./user-profile.component.css']
})
export class UserProfileComponent {
userName: any = null; // Generated property
// ... other properties
}
Explanation: The identifier userName was extracted from the interpolation {{ userName }} and a corresponding property userName: any = null; was added to the UserProfileComponent class.
Example 2: Property Binding
Input (in product-list.component.html):
<app-product-item [product]="currentProduct"></app-product-item>
Action: User selects currentProduct inside [product]="currentProduct".
Output (in product-list.component.ts):
import { Component } from '@angular/core';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.css']
})
export class ProductListComponent {
currentProduct: any = null; // Generated property
// ... other properties
}
Explanation: The identifier currentProduct was extracted from the property binding [product]="currentProduct" and a corresponding property currentProduct: any = null; was added to the ProductListComponent class.
Example 3: Event Binding (Handler Function)
Input (in dashboard.component.html):
<button (click)="handleDashboardSave()">Save</button>
Action: User selects handleDashboardSave inside (click)="handleDashboardSave()".
Output (in dashboard.component.ts):
import { Component } from '@angular/core';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent {
handleDashboardSave: () => void = () => {}; // Generated property
// ... other properties
}
Explanation: The identifier handleDashboardSave was extracted from the event binding (click)="handleDashboardSave()" and a corresponding function property handleDashboardSave: () => void = () => {}; was added to the DashboardComponent class.
Constraints
- The solution should be implementable using the VS Code Extension API (or equivalent APIs for other IDEs that support code actions and TypeScript parsing).
- The solution must correctly parse Angular template syntax for interpolation, property binding, and event binding.
- The solution must be able to locate the associated
.tsfile based on standard Angular naming conventions. - Performance should be reasonable for typical Angular projects, ensuring the code action is available quickly.
Notes
- You will likely need to use a parser for Angular templates (e.g.,
@angular-devkit/architect, or a custom AST parser). - Consider using the VS Code Language Server Protocol (LSP) for implementing code actions.
- The default type
anyand valuenullare placeholders. A more advanced implementation might infer types or suggest common types. - Focus on correctly identifying the expression, extracting the identifier, and modifying the
.tsfile. Error handling for file not found or parsing errors is important.