Last active
September 12, 2025 08:08
-
-
Save etherealHero/dea81b8adccf22eaff5aadb6eddbf0b8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //#region Polyfills & TypeScript utils | |
| if (typeof Object.create !== "function") { | |
| /** | |
| * Polyfill | |
| * {@link https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Object/create#polyfill} | |
| */ | |
| Object.create = (function () { | |
| /** @constructor */ | |
| function Temp() {} | |
| return function (proto) { | |
| if (proto === null || (typeof proto !== "object" && typeof proto !== "function")) { | |
| throw TypeError("Object prototype may only be an Object or null: " + proto); | |
| } | |
| Temp.prototype = proto; | |
| var obj = new Temp(); | |
| Temp.prototype = null; // Avoid keeping a reference to the prototype | |
| return obj; | |
| }; | |
| })(); | |
| } | |
| if (!Function.prototype.bind) { | |
| /** | |
| * Polyfill | |
| * {@link https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#polyfill} | |
| */ | |
| Function.prototype.bind = function ( | |
| // @ts-ignore | |
| oThis | |
| ) { | |
| if (typeof this !== "function") { | |
| // closest thing possible to the ECMAScript 5 | |
| // internal IsCallable function | |
| throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); | |
| } | |
| var aArgs = Array.prototype.slice.call(arguments, 1), | |
| fToBind = this, | |
| fNOP = function () {}, | |
| fBound = function () { | |
| return fToBind.apply(this instanceof fNOP ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); | |
| }; | |
| if (this.prototype) { | |
| // Function.prototype doesn't have a prototype property | |
| fNOP.prototype = this.prototype; | |
| } | |
| // @ts-ignore | |
| fBound.prototype = new fNOP(); | |
| return fBound; | |
| }; | |
| } | |
| /** | |
| * @template T | |
| * @typedef {new (...args: any[]) => T} Constructor | |
| */ | |
| /** | |
| * Extend Derived from Base. | |
| * @template {Constructor<unknown>} Derived | |
| * @template {Constructor<unknown>} Base | |
| * @param {Derived} derivedConstructor - The derived class constructor. | |
| * @param {Base} baseConstructor - The base class constructor. | |
| * @returns {new (...args: ConstructorParameters<Derived>) => Omit<InstanceType<Base>, keyof InstanceType<Derived>> & InstanceType<Derived>} | |
| */ | |
| function extendConstructor(derivedConstructor, baseConstructor) { | |
| derivedConstructor.prototype = Object.create(baseConstructor.prototype); | |
| derivedConstructor.prototype.constructor = derivedConstructor; | |
| // @ts-ignore | |
| return /** @type {any} */ derivedConstructor; | |
| } | |
| /** | |
| * @template T | |
| * @typedef {Omit<T, | |
| * | "constructor" | |
| * | "toString" | |
| * | "toLocaleString" | |
| * | "valueOf" | |
| * | "hasOwnProperty" | |
| * | "isPrototypeOf" | |
| * | "propertyIsEnumerable" | |
| * >} NoObjectProps | |
| */ | |
| /** | |
| * @template T | |
| * @typedef {{ [K in keyof T]: T[K] }} Self | |
| */ | |
| /** | |
| * @template T | |
| * @typedef {Self<{ | |
| * [K in keyof NoObjectProps<T>]: | |
| * T[K] extends (...args: any[]) => any | |
| * ? T[K] // метод с сигнатурой | |
| * : T[K] extends Function | |
| * ? Function // все остальные методы | |
| * : T[K] extends readonly (infer U)[] | |
| * ? Pretty<U>[] // массивы рекурсивно | |
| * : T[K] // остальные поля | |
| * }>} Pretty | |
| */ | |
| /** | |
| * Infer class interface | |
| * @template {Constructor<unknown>} T | |
| * @typedef {Pretty<InstanceType<T>>} IClass | |
| */ | |
| //#endregion | |
| /** @typedef {IClass<Car>} ICar */ | |
| var Car = (function ($) { | |
| var constructor = extendConstructor(Car, $); | |
| /** @this {InstanceType<$> & Car} */ | |
| function Car() { | |
| $.call(this, {}); | |
| this.speed = 1000; | |
| } | |
| return constructor; | |
| })(Object); | |
| /** @constructor @typedef {IClass<User>} IUser */ | |
| var User = (function ($) { | |
| var constructor = extendConstructor(User, $); | |
| /** | |
| * @param {String} name | |
| * @this {InstanceType<$> & User} | |
| */ | |
| function User(name) { | |
| $.call(this, {}); | |
| this.name = name; | |
| } | |
| /** @type {Function} */ | |
| User.prototype.sayAll = /** @this {IUser} */ function () { | |
| throw Error("Unimplemented method"); | |
| }; | |
| /** @param {Pick<IUser, "name">} to */ | |
| User.prototype.sayHello /** @this {IUser} */ = function (to) { | |
| alert("Hello " + to.name + " from " + this.name); | |
| }; | |
| return constructor; | |
| })(Object); | |
| /** @typedef {IClass<Admin>} IAdmin */ | |
| var Admin = (function ($) { | |
| var constructor = extendConstructor(Admin, $); | |
| /** | |
| * @param {String} name | |
| * @param {Number} accessLevel | |
| * @this {InstanceType<$> & Admin} | |
| */ | |
| function Admin(name, accessLevel) { | |
| $.call(this, name); | |
| this.accessLevel = accessLevel; | |
| } | |
| /** | |
| * @param {Number} count of repeat message | |
| */ | |
| Admin.prototype.sayAll = /** @this {IAdmin} */ function (count) { | |
| var stack = []; | |
| for (var i = 0; i < count; i++) { | |
| stack.push("Admin " + this.name + " say all: Hello!"); | |
| } | |
| alert(stack.join(", ")); | |
| }; | |
| return constructor; | |
| })(User); | |
| /** @typedef {IClass<SuperAdmin>} ISuperAdmin */ | |
| var SuperAdmin = (function ($) { | |
| var constructor = extendConstructor(SuperAdmin, $); | |
| /** | |
| * @param {String} name | |
| * @param {Number} accessLevel | |
| * @this {InstanceType<$> & SuperAdmin} | |
| */ | |
| function SuperAdmin(name, accessLevel) { | |
| $.call(this, name, accessLevel); | |
| this.superAdminLevel = 100; | |
| } | |
| SuperAdmin.prototype.sayHello = /** @this {ISuperAdmin} */ function () { | |
| alert("qq"); | |
| return "qq"; | |
| }; | |
| /** | |
| * Отправить всем сообщение | |
| * @param {String} message | |
| * @param {String} postfix | |
| */ | |
| SuperAdmin.prototype.sayAll = /** @this {ISuperAdmin} */ function (message, postfix) { | |
| var msg = "Message " + message + " from " + this.name + postfix; | |
| alert(msg); | |
| return msg; | |
| }; | |
| return constructor; | |
| })(Admin); | |
| /** @typedef {IClass<WorkerUser>} IWorkerUser */ | |
| var WorkerUser = (function ($) { | |
| var constructor = extendConstructor(WorkerUser, $); | |
| /** | |
| * @param {String} name | |
| * @constructor @typedef {InstanceType<$> & WorkerUser} _ @this _ | |
| */ | |
| function WorkerUser(name) { | |
| $.call(this, name); | |
| } | |
| return constructor; | |
| })(User); | |
| /** @type {IUser} */ | |
| var alex = new User("Alex"); | |
| /** @type {IAdmin} */ | |
| var maxim = new Admin("Maxim", 1); | |
| /** @type {IWorkerUser} */ | |
| var irina = new WorkerUser("Irina"); | |
| /** @type {ISuperAdmin} */ | |
| var andrew = new SuperAdmin("Andrew", 2); | |
| /** @type {ICar} */ | |
| var toyota = new Car(); | |
| andrew.name; | |
| andrew.sayHello(); | |
| andrew.sayHello(); | |
| maxim.sayAll(2); | |
| andrew.sayAll("message", "LALALA"); | |
| maxim.sayHello(andrew); | |
| getName(andrew); | |
| getName(toyota); | |
| // Expected diagnostic: | |
| // Argument of type 'Self<{ speed: number; }>' is not assignable to parameter of type | |
| // 'Pick<Self<{ | |
| // name: string; | |
| // sayAll: Function; | |
| // sayHello: (to: Pick<Self<...>, "name">) => void; | |
| // }>, "name">'. | |
| // TypeScript(2345) | |
| nameOfSA(andrew); | |
| /** | |
| * @param {Pick<IUser, "name">} user | |
| * @returns {String} | |
| */ | |
| function getName(user) { | |
| return user.name; | |
| } | |
| /** | |
| * @param {ISuperAdmin} SA | |
| * @returns {String} | |
| */ | |
| function nameOfSA(SA) { | |
| return SA.name; | |
| } | |
| /** | |
| * @template {new (...args: ConstructorParameters<Constructor<InstanceType<typeof User>>>) => IUser} T | |
| * @param {T} userConstructor | |
| * @param {ConstructorParameters<T>} args | |
| * @returns {IClass<T>} new user (infer returns type from input constructor) | |
| */ | |
| function createUser(userConstructor, args) { | |
| var newUser = new (Function.prototype.bind.apply(userConstructor, [null].concat(args)))(); | |
| return newUser; | |
| } | |
| /** | |
| * @template {new (...args: ConstructorParameters<Constructor<InstanceType<typeof User>>>) => IUser} T | |
| * @param {T} userConstructor | |
| * @param {ConstructorParameters<T>} args | |
| * @returns {IUser} strict IUser cast returns type | |
| */ | |
| function createUserStrict(userConstructor, args) { | |
| var newUser = new (Function.prototype.bind.apply(userConstructor, [null].concat(args)))(); | |
| return newUser; | |
| } | |
| // type is IUser | |
| var newUser = createUser(User, ["Pavel"]); | |
| // type is ISuperAdmin | |
| var newSuperAdmin = createUser(SuperAdmin, ["John", 7]); | |
| alert(newSuperAdmin.accessLevel); | |
| // type is IUser | |
| var newSuperAdminAsUser = createUserStrict(SuperAdmin, ["John", 8]); | |
| // Recast allowed: type is IUser | |
| /** @type {IUser} */ | |
| var newSuperAdmin2 = createUser(SuperAdmin, ["John", 9]); | |
| var notAUser = createUser(Car, 1); | |
| // Expected diagnostic: | |
| // Argument of type 'new () => Omit<Object, "speed"> & Car' | |
| // is not assignable to parameter of type | |
| // 'new (...args: any[]) => Self<{ | |
| // name: string; | |
| // sayAll: Function; | |
| // sayHello: (to: Pick<Self<...>, "name">) => void; | |
| // }>'. | |
| // TypeScript(2345) | |
| /** @param {boolean} cond */ | |
| function asserteq(cond) { | |
| return cond ? "PASSED" : "FAIL"; | |
| } | |
| /** @param {boolean} cond */ | |
| function assertne(cond) { | |
| return !cond ? "PASSED" : "FAIL"; | |
| } | |
| alert( | |
| [ | |
| "// все должны быть от Object", | |
| asserteq(toyota /**/ instanceof Object) + '\ttoyota \tinstanceof Object', | |
| asserteq(alex /* */ instanceof Object) + '\talex \tinstanceof Object', | |
| asserteq(maxim /* */ instanceof Object) + '\tmaxim \tinstanceof Object', | |
| asserteq(irina /* */ instanceof Object) + '\tirina \tinstanceof Object', | |
| asserteq(andrew /**/ instanceof Object) + '\tandrew \tinstanceof Object', | |
| "", | |
| "// всё от User", | |
| assertne({} /* */ instanceof Car) + '\t{} \t!instanceof Car ', | |
| assertne({} /* */ instanceof User) + '\t{} \t!instanceof User', | |
| assertne(toyota /**/ instanceof User) + '\ttoyota \t!instanceof User', | |
| asserteq(alex /* */ instanceof User) + '\talex \t instanceof User', | |
| asserteq(maxim /* */ instanceof User) + '\tmaxim \t instanceof User', | |
| asserteq(irina /* */ instanceof User) + '\tirina \t instanceof User', | |
| asserteq(andrew /**/ instanceof User) + '\tandrew \t instanceof User', | |
| "", | |
| "// админы админы", | |
| assertne({} /* */ instanceof Admin) + '\t{} \t!instanceof Admin', | |
| assertne(toyota /**/ instanceof Admin) + '\ttoyota \t!instanceof Admin', | |
| assertne(alex /* */ instanceof Admin) + '\talex \t!instanceof Admin', | |
| assertne(irina /* */ instanceof Admin) + '\tirina \t!instanceof Admin', | |
| asserteq(maxim /* */ instanceof Admin) + '\tmaxim \t instanceof Admin', | |
| asserteq(andrew /**/ instanceof Admin) + '\tandrew \t instanceof Admin', | |
| "", | |
| "// СА только СА", | |
| assertne({} /* */ instanceof SuperAdmin) + '\t{} \t!instanceof SuperAdmin', | |
| assertne(alex /* */ instanceof SuperAdmin) + '\talex \t!instanceof SuperAdmin', | |
| assertne(toyota /**/ instanceof SuperAdmin) + '\ttoyota \t!instanceof SuperAdmin', | |
| assertne(maxim /* */ instanceof SuperAdmin) + '\tmaxim \t!instanceof SuperAdmin', | |
| assertne(irina /* */ instanceof SuperAdmin) + '\tirina \t!instanceof SuperAdmin', | |
| asserteq(andrew /**/ instanceof SuperAdmin) + '\tandrew \t instanceof SuperAdmin', | |
| "", | |
| "// верный констуктор", | |
| asserteq(Var.ClassOf(toyota) /**/ === "Car") /* */ + '\tVar.ClassOf(toyota) \t=== "Car" ', | |
| asserteq(Var.ClassOf(alex) /* */ === "User") /* */ + '\tVar.ClassOf(alex) \t\t=== "User" ', | |
| asserteq(Var.ClassOf(irina) /* */ === "WorkerUser") /**/ + '\tVar.ClassOf(irina) \t\t=== "WorkerUser"', | |
| asserteq(Var.ClassOf(maxim) /* */ === "Admin") /* */ + '\tVar.ClassOf(maxim) \t=== "Admin" ', | |
| asserteq(Var.ClassOf(andrew) /**/ === "SuperAdmin") /**/ + '\tVar.ClassOf(andrew) \t=== "SuperAdmin"', | |
| asserteq(Fnp.TypeOf(toyota) /* */ === "Car") /* */ + '\tFnp.TypeOf(toyota) \t=== "Car" ', | |
| asserteq(Fnp.TypeOf(alex) /* */ === "User") /* */ + '\tFnp.TypeOf(alex) \t=== "User" ', | |
| asserteq(Fnp.TypeOf(irina) /* */ === "WorkerUser") /**/ + '\tFnp.TypeOf(irina) \t=== "WorkerUser"', | |
| asserteq(Fnp.TypeOf(maxim) /* */ === "Admin") /* */ + '\tFnp.TypeOf(maxim) \t=== "Admin" ', | |
| asserteq(Fnp.TypeOf(andrew) /* */ === "SuperAdmin") /**/ + '\tFnp.TypeOf(andrew) \t=== "SuperAdmin"', | |
| "" | |
| ].join("\r\n") | |
| ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment