Skip to content

Instantly share code, notes, and snippets.

@rbuckton
Created October 19, 2017 21:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rbuckton/41bb58736874217880d0894f2a969a14 to your computer and use it in GitHub Desktop.
Save rbuckton/41bb58736874217880d0894f2a969a14 to your computer and use it in GitHub Desktop.

"protected" state

The following is an interpretation of "protected" state exposed through decorators:

NOTE: this has been adapted from https://github.com/tc39/proposal-unified-class-features/blob/master/friend.js

// protected.js
const protectedMembers = new WeakMap();

export function Protected({ key, kind, placement, descriptor }) {
    if (typeof key !== "privatename") throw new TypeError("Only members with private names can be inherited with @Protected.");
    if (kind !== "method" && kind !== "field") throw new TypeError(`Unexpected member kind: ${kind}`);

    // create a new private name for the member.
    const storageName = new PrivateName();
    const storageKey = storageName.key;

    return {
        key,
        kind: "method",
        placement,
        extras: [{ key: storageKey, kind, placement, descriptor }],
        finisher(ctor) { 
            let placements = protectedMembers.get(ctor);
            if (!placements) protectedMembers.set(ctor, placements = new Map());

            let members = placements.get(placement);
            if (!members) placements.set(placement, new Map());
            
            members.set(key.toString(), storageName); 
        }
    };
}

export function Inherit({ key, kind, placement }) {
    if (typeof key !== "privatename") throw new TypeError("Only members with private names can be inherited with @Inherit.");
    if (kind !== "field") throw new TypeError(`Unexpected member kind: ${kind}`);

    let superName;
    return {
        key,
        kind: "method",
        placement,
        descriptor: {
            get() { return superName.get(this); },
            set(value) { superName.set(this, value); }
        },
        finisher(ctor) {
            const keyString = key.toString();
            for (let current = Object.getPrototypeOf(ctor); current !== null; current = Object.getPrototypeOf(ctor)) {
                const placements = protectedMembers.get(current);
                if (placements !== undefined) {
                    const members = placements.get(placement);
                    if (members !== undefined) {
                        superName = members.get(keyString);
                        if (superName !== undefined) {
                            break;
                        }
                    }
                }
            }
            if (superName === undefined) throw new TypeError("Member does not inherit a @Protected member.");
        }
    };
}

Protected members can then be declared using the @Protected decorator and inherited using the @Inherit decorator:

// main.js
import { Protected, Inherit } from "./protected.js";

class Super {
    @Protected #foo = 1;
    @Protected #bar() { return 2; }
}

class Sub extends Super {
    @Inherit #foo;
    @Inherit #bar;
    method() { console.log(this.#foo, this.#bar); }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment