Hone logo
Hone
Problems

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:

  1. @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.
  2. @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 @LogAccess decorator should log messages to console.log.
  • The @Immutable decorator should throw an error of type Error when 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 this context correctly.
  • The @Immutable decorator 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 context object which contains information about the property.
  • Remember that the context.name property will give you the name of the accessor (getter or setter) being decorated.
  • For the @Immutable decorator, 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.
Loading editor...
typescript