Custom String class - javascript

I am looking for a 1-2 punch.
I'd like to typecast custom strings.
Within runtime I'd like to be able to know the type of a string different from a primitive string.
Here's the code:
class TZDatabaseName extends String {
constructor(...args) {
super(...args);
return this;
}
}
expect(new TZDatabaseName('Asia/Tokyo') instanceof String).toBeTruthy();
expect(new TZDatabaseName('Asia/Tokyo') instanceof TZDatabaseName).toBeTruthy();
expect(new TZDatabaseName('Asia/Tokyo')).toEqual('Asia/Tokyo');
I would like all three of the checks below to pass.
I also have been messing with this method of casting strings as well but I have no way of checking in runtime the type of the variable.
export abstract class TZDatabaseName extends String {
public static MAKE(s: string): TZDatabaseName {
if (!s.match(/^\w+\/\w+$/)) throw new Error('invalid TZDatabaseName');
return s as any;
}
private __TZDatabaseNameFlag;
}

Actually, ignore my previous comments about the primitive datatype and object being different, I just tested this myself, and all tests pass? ...
class TZDatabaseName extends String {
constructor(...args) {
super(...args);
return this;
}
}
describe('TZDatabaseName', function() {
it('Instance of String', function() {
expect(new TZDatabaseName('Asia/Tokyo') instanceof String).toBeTruthy();
});
it('Instance of TZDatabaseName', function() {
expect(new TZDatabaseName('Asia/Tokyo') instanceof TZDatabaseName).toBeTruthy();
});
it('Equal to Primitive Type', function() {
expect(new TZDatabaseName('Asia/Tokyo')).toEqual('Asia/Tokyo');
});
});
describe('More TZDatabaseName', function() {
it('Primitive Instance of TZDatabaseName', function() {
expect(''
instanceof TZDatabaseName).toBeFalsy();
});
it('Primitive Instance of String', function() {
expect(''
instanceof String).toBeFalsy();
});
it('String Instance of TZDatabaseName', function() {
expect(String('') instanceof TZDatabaseName).toBeFalsy();
});
});
// Jasmine htmlReporter
(function() {
var env = jasmine.getEnv();
env.addReporter(new jasmine.HtmlReporter());
env.execute();
}());
<link rel="stylesheet" href="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.css" />
<script src="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.js"></script>
<script src="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine-html.js"></script>

Revisiting this a couple years later, it seems I just want to do this:
class TZDatabaseName extends String {
constructor(...args) {
super(...args);
if (!s.match(/^\w+\/\w+$/)) {
throw new Error('invalid TZDatabaseName');
}
return this;
}
}

Related

How to make the static member type of a class point to the type of the subclass

class SingletonBaseClass {
static singleton: SingletonBaseClass | null = null;
static instance() {
if (!this.singleton) {
this.singleton = new this();
}
return this.singleton;
}
static destroy() {
this.singleton = null;
}
}
class Demo extends SingletonBaseClass {
hello() { }
}
Demo.instance().hello();
Above is the code, Demo.instance() will be considered an instance of SingletonBaseClass instead of Demo, so Demo.instance().hello() will report a TypeScript error.
Is there any way to make the return type of Demo.instance() be SingletonBaseClass?
you could just cast it:
(Demo.instance() as Demo).hello();

Return constructor of generic type in TypeScript

I'm struggling to define how to write TypeScipt code which says that function return constructor of generic type. There are plenty of examples around about how to pass constructor of the generic type, but not how to return.
Please check the following example:
This is part of the abstract class:
getModel(): (new () => T) {
throw new Error('Method not implemented.'); // Error because don't know how to fix it
}
When in derived class I'm trying to implement it like this:
getModel(): typeof User {
return User;
}
I have the following error:
Type '() => typeof User' is not assignable to type '() => new () => User'.
I could skip implementation in the derived class if I knew how to specify in the abstract class.
So question is - how to specify on an abstract class level that method returns constructor of the generic type and I can skip implementation of this method at child level class? Or maybe I specify return signature not correctly on the abstract class level?
EDIT:
Please check the strange problem. Class A and B differ only by the presence of explicit constructor. And in RealA doesn't work and RealB works the same getModel() method.
class A {
a = '';
constructor(a: string) {
}
}
class B {
a = '';
static test(): void {
console.log('I do work');
}
}
abstract class Base<T> {
Prop: T;
constructor(TCreator: { new (): T; }) {
this.Prop = new TCreator();
}
getModel(): (new () => T) {
throw new Error('Method not implemented.'); // Error because don't know how to fix it
}
}
class RealA extends Base<A> {
getModel(): typeof A { // doesn't work - compilation error
return A;
}
}
class RealB extends Base<B> {
getModel(): typeof B { // works
return B;
}
}
var test = new RealA(A); // compile error
var test2 = new RealB(B)
For RealA class the same error
() => typeof A' is not assignable to type '() => new () => A'
The error is expected as the constructor for class A has a required argument. The abstract class constrains the constructor to be passed it to have no arguments (new () => T).
The simple solution is to remove the constructor to A.
If you want to be able to pass in classes that have constructors that require arguments you will need to change the definition of the base class to capture the constructor type, and have the constructor take in those required arguments (using tuples in rest parameters)
class A {
a = '';
constructor(a: string) {
}
}
class B {
a = '';
static test(): void {
console.log('I do work');
}
}
type ArgumentTypes<T> = T extends new (...a: infer A) => any? A : []
abstract class Base<T extends new (...a: any[])=> any> {
Prop: InstanceType<T>;
constructor(TCreator: T, ...a: ArgumentTypes<T>) {
this.Prop = new TCreator(...a);
}
getModel(): T {
throw new Error('Method not implemented.'); // Error because don't know how to fix it
}
}
class RealA extends Base<typeof A> {
getModel(): typeof A { // doesn't work - compilation error
return A;
}
}
class RealB extends Base<typeof B> {
getModel(): typeof B { // works
return B;
}
}
var test = new RealA(A, ""); // ok
var test2 = new RealB(B)

Spread class functions

Is there a way to spread the functions of a class into another object? As a contrived example:
class FooBar {
private service: MyService;
constructor(svc: MyService) {
this.service = svc;
}
public foo(): string {
return "foo";
}
public bar(): string {
return "bar"
}
public fooBar(): string {
return "foobar"
}
}
let obj = new FooBar();
export default {
...obj
};
I would want the exported object to contain all the methods of the class FooBar but not the private property service. However, those methods are placed on the prototype object when compiled to javascript so they are not included in the spread operation and the private property is included on the object to it is in the resulting object.
I know I can do this:
export default {
foo: obj.foo.bind(obj),
bar: obj.bar.bind(obj),
fooBar: obj.fooBar.bind(obj),
};
I would like to avoid this if possible as I will have methods from multiple classes to map.
Note: This is to be used for combining GraphQL resolvers into a single object that will be supplied to the graphql function.
I am running my app using ts-node if that makes any difference.
I had a couple of problems going on. First, I was targeting es6 as my output instead of es5. Doing that caused there to be no prototype on the compiled object.
Second, just doing the spread caused the private property service to be included in the exported object. I ended up writing a helper function as alluded to by #Vivick and #AlekseyL.:
function combineResolvers(...resolvers: any[]): any {
let out: { [key: string]: any } = {}
resolvers.forEach(resolver => {
let proto = Object.getPrototypeOf(resolver)
Object.keys(proto)
.filter(key => {
return isFunction(resolver[key]);
}).forEach(key => {
out[key] = resolver[key].bind(resolver)
})
})
return out
}
function isFunction(functionToCheck: any): boolean {
return functionToCheck && {}.toString.call(functionToCheck) === '[object Function]';
}
This still has the problem of including any private functions on the resolver classes into the exported object.
I think this might work; using arrow functions for autobinding the methods and spreading it with Object.assign
class FooBar {
private service: MyService;
constructor(svc: MyService) {
this.service = svc;
}
public foo = (): string => {
return "foo";
}
public bar = (): string => {
return "bar";
}
public fooBar = (): string => {
return "foobar";
}
}
export default { ...Object.assign(new FooBar()) };
But maybe you would want to take a look at this before doing that
https://www.charpeni.com/blog/arrow-functions-in-class-properties-might-not-be-as-great-as-we-think

How to make ES6 class final (non-subclassible)

Assume we have:
class FinalClass {
...
}
How to modify it to make
class WrongClass extends FinalClass {
...
}
or
new WrongClass(...)
to generate an exception? Perhaps the most obvious solution is to do the following in the FinalClass's constructor:
if (this.constructor !== FinalClass) {
throw new Error('Subclassing is not allowed');
}
Does anyone have a more cleaner solution instead of repeating these lines in each class that supposed to be final (probably with a decorator)?
Inspect this.constructor in the constructor of FinalClass and throw if it is not itself. (Borrowing inspection of the this.constructor instead of this.constructor.name from #Patrick Roberts.)
class FinalClass {
constructor () {
if (this.constructor !== FinalClass) {
throw new Error('Subclassing is not allowed')
}
console.log('Hooray!')
}
}
class WrongClass extends FinalClass {}
new FinalClass() //=> Hooray!
new WrongClass() //=> Uncaught Error: Subclassing is not allowed
Alternatively, with support, use new.target. Thanks #loganfsmyth.
class FinalClass {
constructor () {
if (new.target !== FinalClass) {
throw new Error('Subclassing is not allowed')
}
console.log('Hooray!')
}
}
class WrongClass extends FinalClass {}
new FinalClass() //=> Hooray!
new WrongClass() //=> Uncaught Error: Subclassing is not allowed
______
As you say, you could also achieve this behaviour with a decorator.
function final () {
return (target) => class {
constructor () {
if (this.constructor !== target) {
throw new Error('Subclassing is not allowed')
}
}
}
}
const Final = final(class A {})()
class B extends Final {}
new B() //=> Uncaught Error: Subclassing is not allowed
As Patrick Roberts shared in the comments the decorator syntax #final is still in proposal. It is available with Babel and babel-plugin-transform-decorators-legacy.
constructor.name is easy enough to spoof. Just make the subclass the same name as the superclass:
class FinalClass {
constructor () {
if (this.constructor.name !== 'FinalClass') {
throw new Error('Subclassing is not allowed')
}
console.log('Hooray!')
}
}
const OopsClass = FinalClass
;(function () {
class FinalClass extends OopsClass {}
const WrongClass = FinalClass
new OopsClass //=> Hooray!
new WrongClass //=> Hooray!
}())
Better to check the constructor itself:
class FinalClass {
constructor () {
if (this.constructor !== FinalClass) {
throw new Error('Subclassing is not allowed')
}
console.log('Hooray!')
}
}
const OopsClass = FinalClass
;(function () {
class FinalClass extends OopsClass {}
const WrongClass = FinalClass
new OopsClass //=> Hooray!
new WrongClass //=> Uncaught Error: Subclassing is not allowed
}())

Using splice to empty an array with Chromium

I am translating a program from c++ to typescript, and I face a strange behaviour trying to empty an array using the splice technique (How do I empty an array in JavaScript?) to empty an array.
Here is an excerpt of my code in typescript
"use strict"
class UniformGridGeometry<ItemT> extends Array<ItemT> {
itemType: { new (): ItemT; }
constructor(itemType: { new (): ItemT; }) {
// constructor(itemType: { new (): ItemT; }, uGeomTemplate: UniformGridGeometry<any>) // any : Vorton, Particle, Vec*, Mat*, ...
// constructor(itemType: { new (): ItemT; }, uNumElements: number, vMin: Vec3, vMax: Vec3, bPowerOf2: boolean)
// constructor(itemType: { new (): ItemT; }, arg?: any, vMin?: Vec3, vMax?: Vec3, bPowerOf2?: boolean) {
super(); // Array
this.itemType = itemType;
// (...)
}
}
class UniformGrid<ItemT> extends UniformGridGeometry<ItemT> {
constructor(itemType: { new (): ItemT; }) {
// constructor(itemType: { new (): ItemT; }, uGeomTemplate: UniformGridGeometry<any>) // any : Vorton, Particle, Vec*, Mat*, ...
// constructor(itemType: { new (): ItemT; }, uNumElements: number, vMin: Vec3, vMax: Vec3, bPowerOf2: boolean)
// constructor(itemType: { new (): ItemT; }, arg?: any, vMin?: Vec3, vMax?: Vec3, bPowerOf2?: boolean) {
super(itemType);
// (...)
}
}
class NestedGrid<ItemT> extends Array<UniformGrid<ItemT>> {
constructor(src?: UniformGrid<ItemT>) {
super();
if (src) {
this.Init(src);
}
}
Init(src: UniformGrid<ItemT>) {
this.splice(0, this.length) // mUniformGrids.Clear() ;
console.assert(typeof src === 'object', typeof src);
// let numUniformGrids = this.PrecomputeNumUniformGrids( src ) ;
// this.mUniformGrids.Reserve( numUniformGrids ) ; // Preallocate number of UniformGrids to avoid reallocation during PushBack.
let uniformGrid = new UniformGrid<ItemT>(src.itemType);
// uniformGrid.Decimate( src , 1 ) ;
// uniformGrid.Init() ;
this.push(uniformGrid);
// (...)
}
}
function doTests() {
console.info("Test > NestedGrid ; UniformGrid");
let mInfluenceTree: NestedGrid<any> = new NestedGrid<any>(); // Influence tree
let ugSkeleton = new UniformGrid<any>(null);
mInfluenceTree.Init(ugSkeleton);
console.log(mInfluenceTree);
mInfluenceTree.Init(ugSkeleton);
console.log(mInfluenceTree);
}
doTests();
that generates (ES6 target) the following Javascript :
"use strict";
class UniformGridGeometry extends Array {
constructor(itemType) {
super();
this.itemType = itemType;
}
}
class UniformGrid extends UniformGridGeometry {
constructor(itemType) {
super(itemType);
}
}
class NestedGrid extends Array {
constructor(src) {
super();
if (src) {
this.Init(src);
}
}
Init(src) {
this.splice(0, this.length);
console.assert(typeof src === 'object', typeof src);
let uniformGrid = new UniformGrid(src.itemType);
this.push(uniformGrid);
}
}
function doTests() {
console.info("Test > NestedGrid ; UniformGrid");
let mInfluenceTree = new NestedGrid();
let ugSkeleton = new UniformGrid(null);
mInfluenceTree.Init(ugSkeleton);
console.log(mInfluenceTree);
mInfluenceTree.Init(ugSkeleton);
console.log(mInfluenceTree);
}
doTests();
The same code, on firefox or as code snippet works well, but on chromium the assertion fails, the argument 'src' becomes a number (the size of the array in fact) what am I doing wrong ?
(the two Init calls simulates the processing in the WebGL loop)
chromium splice failing
Thanks.
It looks like splice, which creates a new array to return the deleted elements, reuses the class of the element it's called on, hence calling your custom constructor with the desired size.
Here you can fix the problem by using the other way to empty an array: setting its size to 0. Replace
this.splice(0, this.length);
with
this.length = 0;
Another solution might have been to respect the contract of the class you extends as Array's constructor has a different behavior than the one you're implementing in the subclass.
Note that you're in a grey area, regarding specifications. It's probably wiser to avoid extending basic classes like Array.

Categories

Resources