Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Its not good idea to extends Interfaces , classes with reflector functions #9

Open
stepancar opened this issue Aug 26, 2016 · 6 comments

Comments

@stepancar
Copy link

Hello, thank you for this project.

Now we can write somthng like this

interface UiButtonProps {
    description: string;
}

for (let member of UiButtonProps.members) {
    let memberDescription = `Member ${member.name} is ${member.type.kind}`;
    if (member.type.kind === 'interface' || member.type.kind === 'class') {
        memberDescription += ' - name: ' + (<NamedType>member.type).name;
    }
    console.log(memberDescription);
}

But i think it's not good;
Standard reflection framework just provide functions to reflect.
And we can just pass Class or Interface to function;

If your aim - jsut provide intellisence for usage reflector functions - we can create any type like Type in .Net reflection

 interface Type {
     members: Array<any>,
     /...
 }
// and then 

import reflect from 'ts-reflect';
class MyTestClass{
}
reflect(MyTestClass).members.forEach( member=> console.log(member));

I know tha we can't pass interface to function cause it's typescript syntax limitation;

What do you think about it?

@pcan
Copy link
Owner

pcan commented Aug 26, 2016

Actually the solution you proposed in your second snippet is totally feasible, since it's a class:

reflect(MyTestClass).members.forEach( member=> console.log(member));

MyTestClass symbol is valid until emitting time because it's a constructor function.
So, yes we would remove getClass() from Function interface and go with a module function. I'd like to listen other opinions about this. To me, SomeClass.getClass() sounds more fluent, but it's only an opinion :)

Instead, interfaces do not leave anything during emitting phase, so we must have something to handle their "references". We may use strings, but it's not so immediate, it's error prone and not refactor-friendly. What kind of issue you see about using MyInterface as a synthetic constant as it is now?

@stepancar
Copy link
Author

stepancar commented Aug 26, 2016

Now , if we want to create any tool using typescript for reflection - we should to build it using your custom compiler.
But i'ts not easy for most developers. Maybe reflector should be just a function which we can call passing path to source file or to project ?
For example we have any ts project and we want to generate documentation for each classes which extended by ReactComponent class;
example
// MyComponents.ts

import * as React from 'react';
export class FirctComponent extends React.Component<{ member1: number }, {}>{

}

So, i suggest somthing like this
MySuperDocsGenerator.ts / .js

import {reflector} from 'ts-reflector';
reflector('../projects/MySuperProject/src/MyComponents.ts').classes.filter( cl => cl.base.name === 'ReactComponent').map(cl =>  ( cl.base.genericsParams[0]).forEach(propsInterface => {
     propsInterface.members.forEach(member =>{
          console.log(member.name);
          console.log(member.comments);
     });
})

should print
// member1

@pcan
Copy link
Owner

pcan commented Aug 26, 2016

Ok, now I got your point. I know that already exists some library that simply does mere type extraction from a typescript source. Actually this is not the scope of reflec-ts that instead aims to link classes (constructor functions) with their metadata seamlessly. I'll try to elaborate this concept better.

Let's suppose you have a class somewhere hidden in your code and a factory instantiates an object of this class. You are building a DI framework that needs to know if that object's class implements a specific interface because it has to decide at runtime which object inject into another, using interfaces. Here, the only way to get Class metadata for your object is through its object.constructor. Please note that at this point you don't know where this object has been created and, mainly, you don't have any means to access to the class literal class MySuperHiddenClass (that may be module-private, as stated before). So, in some way, the constructor has to bring some additional info with itself that allows you retrieve the Class metadata object (you may want object.constructor.getClass() or Reflection.getClass(object.constructor) but it's not relevant now). Basically, we have to assign an hidden field (preferably using a Symbol as key) to the constructor in order to retrieve its metadata (see Reflection.js, this is what actually happens).

Here, this is the point. Only a compiler can do this thing. Somewhere in the source code, in your super-private module, just after the MySuperHiddenClass class declaration, there must be something like MySuperHiddenClass[mySymbol] = metadataHook (or Reflection.registerClass(MySuperHiddenClass, metadataHook), this is what actually happens). And this is the only chance your code has to create a link between the metadata (that is always available through Reflection.js) and the super-hidden class. The user will never write this magic instruction just after every class. So the compiler comes in our help: during the parsing phase when it detects that you are typing class MySuperHi... on your keyboard, it's already preparing the next instruction Reflection.registerClass(MySuperHi..., metadataHook). When the code will be translated to JS and then executed, you will not notice anything strange or different about your class. It will be private as before, but someone will be keeping a reference to it. This someone is Reflection.js that will allow you to get that constructor also by its string name, if you want.

Let me know if something is not clear, I realized that this is a very complex topic, so feel free to ask and open more issues if you need. I'm happy to contribute to the TypeScript community.

P.S.: it should be not so difficult to use reflec-ts instead of the official TypeScript compiler: the API is the same, the tsc command is the same (just with different name, in order to distinguish it) with same options. Instead of including typescript as reference in your devDependencies you have to include reflec-ts. If there is something not well documented, please report an issue and I'll keep the docs updated. Thank you again for your interest 👍

@pcan pcan closed this as completed Aug 26, 2016
@pcan pcan reopened this Aug 26, 2016
@cvle
Copy link

cvle commented Oct 15, 2016

I know that already exists some library that simply does mere type extraction from a typescript source.

I am interested in this project for the exact purpose of type extraction from interfaces for documentation purposes. I am not aware of any other tool that works with newer version of typescript. Which libraries are you referring to?

@pcan
Copy link
Owner

pcan commented Oct 16, 2016

Which libraries are you referring to?

You may want to try ts-type-info.

@cvle
Copy link

cvle commented Oct 16, 2016

Thanks, I'll check it out!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants