TypeScript Compiler API: How to get type with resolved type arguments? - typescript-compiler-api

I want to merge class declarations in a .dt.s file to generate a cleaner public API. I am stuck on how to make this work with generic type arguments. Let's say I have:
class A1<T> { // Non-exported class I want to hide
data?: T;
}
export class B1 extends A1<string> {
}
Ideally, I want to turn this into:
export class B1 {
data?: string;
}
I can get the type of A1 and then copy its members. But how do get a resolved version of A1 that uses string instead of T?
For reference, this is my current code:
for (const heritageClause of node.heritageClauses) {
for (const type of heritageClause.types) {
if (isExported(type.modifiers)) {
exportedTypes.push(type);
} else {
const privateType = typeChecker.getTypeAtLocation(type);
if (privateType?.symbol?.members) {
privateType.symbol.members.forEach((definition, memberName) => {
if (!currentMembers || !currentMembers.has(memberName)) {
additionalMembers.push(...definition.declarations);
}
}
});
}
}
}

I believe the method you are looking for is TypeChecker#getTypeOfSymbolAtLocation(symbol, node).
The following should get the resolved type of string | undefined:
// by the way, recommend renaming `type` to `typeNode` to avoid confusion
typeChecker.getTypeOfSymbolAtLocation(privateType.getProperties()[0], type);

Related

Haxe: Binding pattern with abstract fields access methods

I'd like to make wrapper to implement simple data binding pattern -- while some data have been modified all registered handlers are got notified. I have started with this (for js target):
class Main {
public static function main() {
var target = new Some();
var binding = new Bindable(target);
binding.one = 5;
// binding.two = 0.12; // intentionally unset field
binding.three = []; // wrong type
binding.four = 'str'; // no such field in wrapped class
trace(binding.one, binding.two, binding.three, binding.four, binding.five);
// outputs: 5, null, [], str, null
trace(target.one, target.two, target.three);
// outputs: 5, null, []
}
}
class Some {
public var one:Int;
public var two:Float;
public var three:Bool;
public function new() {}
}
abstract Bindable<TClass>(TClass) {
public inline function new(source) { this = source; }
#:op(a.b) public function setField<T>(name:String, value:T) {
Reflect.setField(this, name, value);
// TODO notify handlers
return value;
}
#:op(a.b) public function getField<T>(name:String):T {
return cast Reflect.field(this, name);
}
}
So I have some frustrating issues: interface of wrapped object doesn't expose to wrapper, so there's no auto completion or strict type checking, some necessary attributes can be easily omitted or even misspelled.
Is it possible to fix my solution or should I better move to the macros?
I almost suggested here to open an issue regarding this problem. Because some time ago, there was a #:followWithAbstracts meta available for abstracts, which could be (or maybe was?) used to forward fields and call #:op(a.b) at the same time. But that's not really necessary, Haxe is powerful enough already.
abstract Binding<TClass>(TClass) {
public function new(source:TClass) { this = source; }
#:op(a.b) public function setField<T>(name:String, value:T) {
Reflect.setField(this, name, value);
// TODO notify handlers
trace("set: $name -> $value");
return value;
}
#:op(a.b) public function getField<T>(name:String):T {
trace("get: $name");
return cast Reflect.field(this, name);
}
}
#:forward
#:multiType
abstract Bindable<TClass>(TClass) {
public function new(source:TClass);
#:to function to(t:TClass) return new Binding(t);
}
We use here multiType abstract to forward fields, but resolved type is actually regular abstract. In effect, you have completion working and #:op(a.b) called at the same time.
You need #:forward meta on your abstract. However, this will not make auto-completion working unless you remove #:op(A.B) because it shadows forwarded fields.
EDIT: it seems that shadowing happened first time I added #:forward to your abstract, afterwards auto-completion worked just fine.

Kotlin, how to retrieve field value via reflection

So I have hundreds of fields in a couple of classes and I'd like to write some methods on them where they automatically println each field and its corresponding value
At the moment I have this:
inner class Version(val profile: Profile) {
#JvmField val MINOR_VERSION = glGetInteger(GL_MINOR_VERSION)
fun write(file: File? = null) {
//file.printWriter().use { out -> out.pri }
this::class.java.fields.forEach {
println(it.isAccessible)
println(it.getInt(it)) }
}
}
But this is what I get:
false
Exception in thread "main" java.lang.IllegalArgumentException: Can not set final int field uno.caps.Caps$Version.MINOR_VERSION to java.lang.reflect.Field
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:58)
at sun.reflect.UnsafeQualifiedIntegerFieldAccessorImpl.getInt(UnsafeQualifiedIntegerFieldAccessorImpl.java:58)
Any idea?
Instead of using Java fields and Java reflection code, you can also use Kotlin properties and Kotlin reflection classes:
class Reflector {
val Foo = 1;
fun printFields() {
this::class.memberProperties.forEach {
if (it.visibility == KVisibility.PUBLIC) {
println(it.name)
println(it.getter.call(this))
}
}
}
}
It seems that you are passing the Field variable it as a parameter getInt whereas the parameter should be the object the field belongs to this:
From the Javadoc for Field.getInt(Object obj):
obj - the object to extract the int value from
Perhaps this is what you meant to do:
class Reflector {
#JvmField val Foo = 1;
fun printFields() {
this.javaClass.fields.forEach {
println(it.isAccessible)
println(it.getInt(this))
}
}
}
fun main(args : Array<String>) {
Reflector().printFields()
}

Aurelia creating a Binding Behaviour that wraps SignalBindingBehaviour

I've currently got a simple Value Converter which uses momentjs to convert Dates to strings:
export class MomentValueConverter {
public toView(value: Date, format: string): string {
return moment(value).format(format);
}
}
However, wherever I use it I end up having to combine it with the aurelia-translation-signal so that its updated if the user changes the current language.
${fileSaved | moment:'ll LTS' & signal:'aurelia-translation-signal'}
How do I instead create a Binding Behavior that automatically takes care of the signalling from aurelia-translation-signal?
Then I could use it like:
${fileSaved & moment:'ll LTS'}
There is a good example in aurelia-i18n library, https://github.com/aurelia/i18n/blob/master/src/t.js#L89-L122
import {ValueConverter} from 'aurelia-binding';
import {SignalBindingBehavior} from 'aurelia-templating-resources';
export class TBindingBehavior {
static inject = [SignalBindingBehavior];
constructor(signalBindingBehavior) {
this.signalBindingBehavior = signalBindingBehavior;
}
bind(binding, source) {
// bind the signal behavior
this.signalBindingBehavior.bind(binding, source, 'aurelia-translation-signal');
// rewrite the expression to use the TValueConverter.
// pass through any args to the binding behavior to the TValueConverter
let sourceExpression = binding.sourceExpression;
// do create the sourceExpression only once
if (sourceExpression.rewritten) {
return;
}
sourceExpression.rewritten = true;
let expression = sourceExpression.expression;
sourceExpression.expression = new ValueConverter(
expression,
't',
sourceExpression.args,
[expression, ...sourceExpression.args]);
}
unbind(binding, source) {
// unbind the signal behavior
this.signalBindingBehavior.unbind(binding, source);
}
}
UPDATE: Signals are internally supported by value converters already http://aurelia.io/docs/binding/value-converters#signalable-value-converters
import {signalBindings} from 'aurelia-framework';
signalBindings('locale-changed');
export class FlightTimeValueConverter {
signals = ['locale-changed'];
toView(date) {
return date.toLocaleString(window.currentLocale);
}
}

How do I fix this "Constructor cannot be called on object type" error in flow?

I'm having trouble figuring out the problem that flow is complaining about. I'm trying to allow the implementation of an API be changeable by storing the implementation class, then later instantiating it, however, flow complains when I call new this.implKlass saying that "Constructor cannot be called on object type". What is flow trying to tell me, and what am I conceptually missing about how flow works?
Example code below, and flow try code here
/* #flow */
type ApiT = {
fnA(): Promise<*>;
}
// An implementation of the API
class Impl {
async fnA(): Promise<*> { return 1; }
}
class DoThings {
implKlass: ApiT;
constructor(klass) {
this.implKlass = klass;
}
callA() {
const Klass = this.implKlass;
const inst = new Klass();
return inst.fnA();
}
}
new DoThings(Impl).callA();
Example output:
18: const inst = new Klass();
^ constructor call. Constructor cannot be called on
18: const inst = new Klass();
^ object type
13: constructor(klass: ApiT) {
^ property `fnA`. Property not found in
23: new DoThings(Impl).callA();
^ statics of Impl
With a small modification this works.
class DoThings {
implKlass: Class<ApiT>;
constructor(klass) {
this.implKlass = klass;
}
callA() {
const Klass = this.implKlass;
const inst = new Klass();
return inst.fnA();
}
}
The bug was you were writing ApiT instead of Class<ApiT>. ApiT would be an instance of a class, while Class<ApiT> is the class itself.
Try flow link
ApiT describes an object type, not a class type. An instance of the Impl class satisfies the ApiT type, but the class Impl itself does not. You cannot call Impl.fnA(), for example.
I'm not sure if there is any way to pass around constructors like this. However you can accomplish basically the same thing by using a factory function:
type ApiT = {
fnA(): Promise<*>;
}
type ApiTFactory = () => ApiT;
class Impl {
async fnA(): Promise<*> { return 1; }
}
class DoThings {
factory: ApiTFactory;
constructor(factory: ApiTFactory) {
this.factory = factory;
}
callA() {
const factory = this.factory;
const inst = factory();
return inst.fnA();
}
}
new DoThings(() => new Impl()).callA();
tryflow link

TypeScript - passing a class as an argument, and reflection

I am writing a generic unmarshaller. It converts graph DB data to generated TypeScript (1.8.7) model classes. The input is JSON. The output should be an instance of a model class.
My ultimate goal is to create something like Hibernate OGM, only for Tinkerpop Frames and TypeScript, with REST endpoint in the middle.
What's the right way to pass a class as a parameter and reach it's static members? I want to have something like this:
SomeModel some = <SomeModel> unmarshaller.fromJSON({/*Object from JSON*/}, SomeModel);
I've tried to write a method.
Not sure if I am heading in the right direction, feel free to suggest different approaches.
public fromJSON(input: Object, clazz: typeof FrameModel): FrameModel
{
// This only demonstrates access to Framemodel's metadata
// about original Java model classes.
clazz.graphPropertyMapping;
clazz.graphRelationMapping;
let result = {};
...
return result;
}
...
But when I tried to execute this on Plunker, I got execution errors with unuseful stacktrace.
The model superclass looks like this:
/**
* Things common to all Frames models on the Typescript side.
*/
export class FrameModel
{
// Model metadata
static discriminator: string;
static graphPropertyMapping: { [key:string]:string; };
static graphRelationMapping: { [key:string]:string; };
// Each instance needs a vertex ID
private vertexId: number;
public getVertexId(): number {
return this.vertexId;
}
}
Sample model class:
import {TestPlanetModel} from './TestPlanetModel';
import {TestShipModel} from './TestShipModel';
export class TestGeneratorModel extends FrameModel
{
static discriminator: string = 'TestGenerator';
static graphPropertyMapping: { [key:string]:string; } = {
bar: 'boo',
name: 'name',
rank: 'rank',
};
static graphRelationMapping: { [key:string]:string; } = {
colonizes: 'colonizedPlanet',
commands: 'ship',
};
boo: string;
name: string;
rank: string;
public colonizedPlanet: TestPlanetModel[]; // edge label 'colonizedPlanet'
public ship: TestShipModel; // edge label 'ship'
}
I haven't found much material on reflection and class handling in TypeScript.
I know how I would do this in Java.
I know how I would do this in JavaScript.
I understand that I might achieve similar results with decorators, but having fields or static fields seemed a bit simpler, for generated models.
You've maybe already noticed that class members cannot have const keyword. But you could go with static instead. Also member should be public if you want it to be accessible from outside world.
public static graphPropertyMapping: { [key:string]:string; } = {
bar: 'boo',
name: 'name',
rank: 'rank',
};
As for creating result instance:
let result = new clazz();
//copy properties
return result;
If I understand you correctly then here's something to help you get started:
interface Model {}
interface ModelData {}
interface MyModelConstructor<M extends Model, D extends ModelData> {
new(data: D): M;
// static members
graphPropertyMapping: any;
graphRelationMapping: any;
}
class Unmarshaller {
public fromJSON<T>(input: string | ModelData, ctor: MyModelConstructor<T, ModelData>): T {
let data: ModelData = (typeof input === "string") ? JSON.parse(input) : input;
let propertyMapping = ctor.graphPropertyMapping;
let relationMapping = ctor.graphRelationMapping;
// do whatever with the mappings
return new ctor(input);
}
}
(code in playground)
I don't know how your models look like, so I hope this is close enough.
I recently released an enhanced version of the TypeScript compiler that allows exactly what you are expecting: read all (static or not) fields metadata from a class. For example you can write:
interface MyInterface {
active:boolean;
description: string;
}
class MyClass {
id: number;
name: string;
myComplexField: MyInterface;
}
function printMembers(clazz: Class) {
let fields = clazz.members.filter(m => m.type.kind !== 'function'); //exclude methods.
for(let field of fields) {
let typeName = field.type.kind;
if(typeName === 'class' || typeName === 'interface') {
typeName = (<Class | Interface>field.type).name;
}
console.log(`Field ${field.name} of ${clazz.name} has type: ${typeName}`);
}
}
printMembers(MyClass.getClass());
this is the output:
$ node main.js
Field id of MyClass has type: number
Field name of MyClass has type: string
Field myComplexField of MyClass has type: MyInterface
Of course, if you change the members property access of clazz to statics you will retrieve all static members. These information can be accessed at coding time too, so you can use autocompletion.
You can do the same with Interfaces metadata. Simply write MyInterface for example, and access its members.
You can find the project here.

Resources