TypeScript Accessor Decorators
This challenge will guide you through implementing accessor decorators in TypeScript. Accessor decorators are a powerful feature that allows you to add custom behavior to getters and setters of class properties. This is incredibly useful for tasks like validation, logging, or lazy initialization.
Problem Description
Your task is to create a system that allows you to define and apply accessor decorators to class properties in TypeScript. Specifically, you need to implement two example accessor decorators:
@LogAccess: This decorator should log a message to the console whenever the decorated property's getter or setter is invoked. The log message should indicate whether it's a "get" or "set" operation and include the property name.@Immutable: This decorator should prevent any attempts to set the value of the decorated property after its initial assignment. If a subsequent attempt is made to set the value, it should throw an error.
Key Requirements
- You must create TypeScript accessor decorators.
- Decorators should be applied to class properties that have both a getter and a setter.
- The
@LogAccessdecorator should log messages toconsole.log. - The
@Immutabledecorator should throw an error of typeErrorwhen an invalid set operation occurs. - Your solution should be compatible with standard TypeScript compilation (e.g., using
tsc --experimentalDecorators --emitDecoratorMetadata -t es5 your_file.ts).
Expected Behavior
When a class with decorated properties is instantiated and its properties are accessed, the decorators should execute their defined logic.
Examples
Example 1: Basic Usage of @LogAccess
class User {
private _name: string = "Guest";
@LogAccess
get name(): string {
return this._name;
}
@LogAccess
set name(value: string) {
this._name = value;
}
}
const user = new User();
console.log("Initial name:", user.name); // Logs "Getting name..." then "Initial name: Guest"
user.name = "Alice"; // Logs "Setting name..."
console.log("Updated name:", user.name); // Logs "Getting name..." then "Updated name: Alice"
Output:
Getting name...
Initial name: Guest
Setting name...
Getting name...
Updated name: Alice
Example 2: Basic Usage of @Immutable
class Settings {
private _timeout: number;
constructor(timeout: number) {
this._timeout = timeout;
}
@Immutable
get timeout(): number {
return this._timeout;
}
@Immutable
set timeout(value: number) {
this._timeout = value;
}
}
const settings = new Settings(1000);
console.log("Initial timeout:", settings.timeout); // Output: Initial timeout: 1000
try {
settings.timeout = 2000; // This should throw an error
} catch (e) {
console.error("Error:", e.message); // Output: Error: Property 'timeout' is immutable.
}
console.log("Timeout after attempted set:", settings.timeout); // Output: Timeout after attempted set: 1000
Output:
Initial timeout: 1000
Error: Property 'timeout' is immutable.
Timeout after attempted set: 1000
Example 3: Combining Decorators
class Product {
private _price: number = 0;
private _isDiscontinued: boolean = false;
@LogAccess
@Immutable // Apply Immutable first, then LogAccess
get price(): number {
return this._price;
}
@LogAccess
@Immutable
set price(value: number) {
this._price = value;
}
@LogAccess
get isDiscontinued(): boolean {
return this._isDiscontinued;
}
@LogAccess
set isDiscontinued(value: boolean) {
this._isDiscontinued = value;
}
}
const product = new Product();
console.log("Initial price:", product.price); // Logs "Getting price..." then "Initial price: 0"
try {
product.price = 50; // Logs "Setting price..." then throws error
} catch (e) {
console.error("Error:", e.message); // Output: Error: Property 'price' is immutable.
}
console.log("Price after attempted set:", product.price); // Logs "Getting price..." then "Price after attempted set: 0"
product.isDiscontinued = true; // Logs "Setting isDiscontinued..."
console.log("Discontinued status:", product.isDiscontinued); // Logs "Getting isDiscontinued..." then "Discontinued status: true"
Output:
Getting price...
Initial price: 0
Setting price...
Error: Property 'price' is immutable.
Getting price...
Price after attempted set: 0
Setting isDiscontinued...
Getting isDiscontinued...
Discontinued status: true
Constraints
- Decorators must be implemented as functions that return a decorator factory.
- Decorators should handle the
thiscontext correctly. - The
@Immutabledecorator should only prevent modifications after the initial assignment. It should not prevent the initial assignment during construction or initialization. - Ensure your code compiles with
tsc --experimentalDecorators --emitDecoratorMetadata -t es5 your_file.ts.
Notes
- Accessor decorators in TypeScript are applied to getters and setters individually. You will receive the
contextobject which contains information about the property. - Remember that the
context.nameproperty will give you the name of the accessor (getter or setter) being decorated. - For the
@Immutabledecorator, you'll need a way to track whether the property has been set once. A common approach is to use a closure or a WeakMap to store this state associated with the instance. - Consider the order of decorator application when multiple decorators are applied to the same accessor. Decorators are applied in reverse order of their declaration.