Is using the super keyword allowed on static properties of classes? - javascript

I know that the super keyword can be used to reference inherited properties and methods in a class. It can also be used in static methods and call super.staticMethod(). Is it okay to use the super keyword on static properties? Rollup says that it isn't okay, and I came across this problem while compiling ESModules to a CommonJs file.
Rollup is fine with this
class A {
static m() {
return 1;
}
}
class B extends A {
static m() {
return super.m() + 1;
}
}
console.log("A", A.m());
console.log("B", B.m());
Here there are static methods on classes A and B. B calls super.m(), which is like calling A.m().
Rollup is NOT fine with this
class A {
static score = 5;
static info = {
prop1: "hi",
prop2: "hello"
}
}
class B extends A {
static score = super.score + 5;
static info = {
...super.info,
prop3: "welcome",
prop4: "good morning"
}
}
console.log("A", A.score, A.info);
console.log("B", B.score, B.info);
Still, I don't see what's wrong with the second example. When I run it, it works without any errors. Is it bad or invalid javascript, or is rollup just complaining even though it's valid javascript? When I replace super with A (for example, A.score instead of super.score), it works.
Rollup Error
[!] Error: 'super' keyword outside a method
My rollup.config.js file
export default {
input: "index.js",
output: {
file: "commonjs/index.js",
format: "cjs"
}
}

Static class fields are still an experimental proposal.
Is it okay to use the super keyword on static properties?
Yes, probably.
If I interpret the current specification draft correctly, there is no early error from using super.… or super[…] in a class field, only super(…) and arguments are syntax errors - and there are no early errors specifically for static fields either. The field initialisers are evaluated as if their code was in an anonymous method, where they have access to a receiver (this value) and home object (super base).
However, there appears to be an editorial issue about how the class value is passed to the definition algorithm, and it's not inconceivable that the decision to allow super is reevaluated. That the parser used by Rollup disagrees with Chrome here is a bug, but we don't know in which of the two implementations.

Related

TypeScript: How to get inheritance type of class

So I'm writing a compiler, and different "Statement" types have different classes. A Block Statement has a BlockStatement class, an If Statement has an IfStatement class, etc.
I need to be able to tell what type of object I'm working with at runtime, eg
class BlockStatement extends Statement {
constructor(...children: Statement[]) {
super()
this.rep.assemble(
new BlockLabel(),
children.map(child => Statement.extractRep(child))
)
}
private addToBlock(pos: number, s: Statement): void {
this.rep.addChild(pos, Statement.extractRep(s))
}
}
function prettyPrint(s: Statement) {
switch () {
case 'BlockStatement': {
}
}
}
How can I tell what type of Statement I am working with? And even if I can tell, will it not matter since it may slice any inherited functionality since the parameter is a statement?
###object###.constructor.name does the trick, however, ive heard minifying may cause issues with this. Then cast itself as whatever it is so intellisense works

Adding a function to the Object prototype and making it available to all modules

I am doing some experimentation with Typescript and I am trying to add a property to the Object prototype so that it is available to all objects in all of my modules.
Here is what I have so far:
In a Common.ts file
Object.defineProperty(Object.prototype, 'notNull', {
value: function(name: string){
if(this === null || this === undefined){
throw new Error(`${name} cannot be null nor undefined`);
}
return this;
},
enumerable: false
});
Now I would like to use it in another file like so:
module SomeModule{
class Engine{
constructor(public horsePower: number, public engineType: string){}
}
class Car{
private _engine: Engine;
constructor(private engine: Engine){
//Of course here the compiler complains about notNull not existing
this._engine = engine.notNull('engine');
}
}
}
Now I am at a loss since I am not sure that by exporting "Object" with module.exports in Common.ts makes sense at all. And even if I do that and import it on my other file, that does not seem to do anything.
Is there a way to implement something like this?
Thank you.
When you change the Object.prototype it will affect everything in the environment that is running your code, which is why you'd usually be advised to avoid extending native types.
With that being said, if you do want to walk this path, then here's what you need to do:
As I said, once you change the prototype it will already be available at runtime, but the compiler isn't yet aware of the change so it will complain when you try to use this new method.
To get around that, you need to use global augmentation:
declare global {
interface Object {
notNull(name: string): this;
}
}
Edit
As the comment to this answer suggested, this approach only works if you're using modules (import/export), but if you don't then you need to do this:
interface Object {
notNull(name: string): this;
}
And put it in a place that is used in any file in which you're trying to use notNull, for example in a .d.ts file that is referenced in all your ts source files.
2nd Edit
To use modules you'll just do:
export class Engine{
constructor(public horsePower: number, public engineType: string){}
}
export class Car {
private _engine: Engine;
constructor(private engine: Engine){
//Of course here the compiler complains about notNull not existing
this._engine = engine.notNull('engine');
}
}
Without sourounding it with module SomeModule { ... }

How to type annotate the constructor of "this" in Javascript with FlowType

I'm having an issue with Javascript and FlowType. What I'm trying to do is return the static class so that I can easily refer to class methods within instance methods using code like this.cls.staticProperty Take the following example:
// #flow
class A {
// This is where it fails... It cannot return Class<this>
// ERROR: Flow: `this` type. invariant position (expected `this` to occur only covariantly)
get cls(): Class<this> {
return this.constructor
}
}
Now, I know that I could simply fix this by specifying A explicitly, but then I get problems in children classes
// #flow
class A {
// This works
get cls(): Class<A> {
return this.constructor
}
}
class B extends A {
static myProp: string
doSomething(): void {
// But, now, this doesn't work
// ERROR: Flow: property `myProp`. Property not found in statics of A
this.cls.myProp = "Hello World"
}
}
I could now fix this by specifying the cls method on B as well like so:
// #flow
class A {
// This works
get cls(): Class<A> {
return this.constructor
}
}
class B extends A {
static myProp: string
// I have to define this again for class B
get cls(): Class<B> {
return this.constructor
}
doSomething(): void {
// Works now, but now I'm violating the DRY principle
this.cls.myProp = "Hello World"
}
}
Or, I could simply return Class<any>, like so:
// #flow
class A {
// Now, we are returning the constructor of any
get cls(): Class<any> {
return this.constructor
}
}
class B extends A {
static myProp: string
doSomething(): void {
// Works now, but now my IDE doesn't give me auto complete
// And I'm having to manually look up static properties and methods
this.cls.myProp = "Hello World"
}
}
But, now my IDE doesn't give me any type hints or auto complete properties on the class so I'm stuck having to type properties and methods out from memory which is error prone, not to mention how much time it takes to continually look up static methods and properties
Another solution I tried was just getting rid of the cls getter and instead just specifying the actual class name when I need a static property, but there are problems there too.... Consider this code:
// #flow
class A {
static myProp: string
get staticMyProp(): string {
return A.myProp
}
}
A.myProp = "A Hello World"
class B extends A {
}
B.myProp = "B Hello World"
let bObj = new B()
console.log(bObj.staticMyProp) // Logs "A Hello World"
// But we expected "B Hello World" since we are running it from the B instance
And one last option.... I could just specify this.constructor every time.... But I really don't want to do that... I'd rather use this.cls because that's what I'm used to (coming from a Python background)
// #flow
class A {
static myProp: string
get staticMyProp(): string {
return this.constructor.myProp
}
}
A.myProp = "A Hello World"
class B extends A {
}
B.myProp = "B Hello World"
let bObj = new B()
console.log(bObj.staticMyProp) // Logs "B Hello World" as expected
I really want to be able to get the get cls(): Class<this> (First example above) option to work, with flow type, any suggestions? Or am I out of luck?
If you don't want to violate DRY, don't use typical inheritance hierarchies to solve common problems. It only leads to pain and heartache and very messy refactoring. In JavaScript / TypeScript, this is 4000% more true than in other "OO" languages.
That said, there may be some means of convincing Flow to let you pass, via F-bounded polymorphism, but it's really not going to like this, at all. Regardless of how you write this, Flow (or other sound type systems) are going to complain that you did.

Declaring dynamic prototype methods to typescript

I'm converting a javascript class with several "generated" prototype methods to typescript. The generated methods are mapped to an internal object so the API is cleaner/more convenient for 80% of its use cases.
However, I'm find no way to properly tell typescript which methods exist without actually implementing them.
class A {}
A.prototype.test = function() {}
var a = new A().test();
It errors with error TS2339: Property 'test' does not exist on type 'A'
It seems like I can get around it by defining the property manually, but that hinders the usefulness of automatically mapping these methods.
class A {
test: any;
}
A.prototype.test = function() {}
var a = new A();
a.test();
However, I'm find no way to properly tell typescript which methods exist without actually implementing them.
If the methods are generated why even define the class? i.e. instead of class A you really should only be declaring the class i.e. you should be doing:
declare class A {
test();
}
I can get around it by defining the property manually
Yes. Because of the dynamic part, the type cannot be statically inferred by TypeScript. It has to be manually defined:
class A {
test: () => void;
}
A.prototype.test = function() {}
Or, another way that I use sometimes:
Dynamically build the API in TypeScript module files, that export some members;
Compile to JavaScript code;
Write a file index.d.ts that declares the exported module members of the API (like the answer of #basarat);
In a separated project (with another tsconfig.json), import the module members of the API, from the JavaScript code, with the help of the declarations in index.d.ts.
This process is particularly suited to publish a npm package.

How to protect functions which are called with different contexts from breaking?

I'm fairly new to javascript and now I learned how calling functions with a context works.
Here is a simple example that poses a question in my head. Lets say we have this example:
var myObj = {
bar: function() {
console.log("Lets got to the bar!");
}
}
/* how can this be protected from errors,
* if a passed object doesn't contain bar */
function foo()
{
this.bar();
}
foo.call(myObj);
Now, how can foo be protected of breaking? In some OOP language (lets say Java) this would be implemented lets say via an interface. So in that case if the object being instantiated hasn't implemented the interface method, the compiler would through an error so the compiler protects the code/program from being faulty (in this case of course).
public interface MyInterface
{
public void bar();
}
public class MyClass implements MyInterface
{
public void bar()
{
System.println("Lets go to the bar");
}
}
MyInterface m = new MyClass();
m.bar(); // if bar isn't implemented the compiler would warn/break
Note: I'm not that good in Java so sorry for any syntax or other errors, but I hope you get the point.
So to sum up, as I see that in both cases in both languages one can achieve polymorphism, right? Now if so for the Javascript example, how can one protect it from breaking, are there any patterns or tricks? Does typeof this.bar === function work? If so, who guarantees the SW quality if the programmer forgets this, I'm asking this kind of question because Java has the compiler to warn the programmer about the mistake, does JS have something similar, some quality check tool?
Javascript is a dynamic interpeted* language. There isn't a compiler step to check references. Some tools (jsline) and IDEs (VS, Webstorm) can perform some design-time checks for you, but there's no true type safety. This is largely seen as a feature, not a bug.
There's an array of tricks to work around this (.hasOwnProperty, typeof x === 'function', storing self references, context binding) but mostly, if you want a type safety, you want a different language.
My recommendation is Typescript. It has a Java/C-like syntax, with some familiar OOP features, like classes, interface (and thus, sane polymorphism) and generic types, and transpiles to javascript in moments.
If you use a constructor to create your object you can use Javascript's builtin class member ship checking features. An example is below.
class MyClass {
bar() { console.log("Lets got to the bar!")}
}
function foo() {
if ( this instanceof MyClass ) {
this.bar();
}
else {
console.log('this is not a member of the MyClass');
}
}
foo.call(new MyClass);
Be warned that Javascript's type checking is horribly unreliable and you probably should not use it. If your object contains the same prototype anywhere in it's prototype chain as the class you are testing it for membership in, instanceof will return true.
Quick and dirty duck typing example
This will throw if you give it an object without the properties you are checking for, but you get the idea.
class MyClass {
constructor() {
this.feathers = 'white';
this.feet = 'webbed';
}
bar() { console.log("Lets got to the bar!")}
}
function foo() {
if (
this.hasOwnProperty('feathers') &&
this.hasOwnProperty('feet') &&
this.feathers === 'white' &&
this.feet === 'webbed'
)
{
this.bar();
}
else {
console.log('this is not a member of the MyClass');
}
}
foo.call(new MyClass);

Categories

Resources