Hone logo
Hone
Problems

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:

  1. 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.
  2. 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)" (where myHandler is the potential property)
  3. Property Generation:
    • The code action should extract the myProperty (or myHandler) identifier from the selected expression.
    • It should then navigate to the associated .ts file for the component.
    • A new property declaration should be added to the component class.
    • The generated property should have a default type (e.g., any or string) and a default value (e.g., null or ''). The developer can then refine this.
  4. File Association: The extension needs to be able to determine the corresponding .ts file for a given .html template. This typically follows a naming convention (e.g., my-component.component.html and my-component.component.ts).
  5. 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 }} in my-component.component.html and triggers the code action, a new line my-data: any = null; (or similar) should be added to my-component.component.ts within the MyComponent class.
  • When a user selects [user]="currentUser" in my-component.component.html and triggers the code action, a new line user: any = null; should be added to my-component.component.ts.
  • When a user selects (click)="onButtonClick()" and triggers the code action, a new line onButtonClick: () => void = () => {}; should be added to my-component.component.ts.

Edge Cases:

  • Multiple matches: If an identifier appears multiple times in the template, the action should generate the property in the .ts file regardless.
  • Existing property: If a property with the same name already exists in the .ts file, 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 .ts file 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 .ts file 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 any and value null are placeholders. A more advanced implementation might infer types or suggest common types.
  • Focus on correctly identifying the expression, extracting the identifier, and modifying the .ts file. Error handling for file not found or parsing errors is important.
Loading editor...
typescript