Typescript custom tool, transpiler, extension

I would like to alter/check .ts files before tsc will start its transpilation process, similar to what Roslyn provides for C#.

This is useful for static type checking. For example, proper and short Immutable Record implementation requires code altering/static check at compile time, as it can be done with Flow:

@Record(Person)
interface IPerson {  
  givenName: string;
  familyName: string;
}

and then custom tsc transpiler could modify code to:

interface IPersonParams {  
  givenName?: string;
  familyName?: string;
}
@Record()
class Person {  
  private readonly __givenName;
  private readonly __familyName;
  constructor(init) {
    this.__givenName = init.givenName;
    this.__familyName = init.familyName;
  }
  get givenName() {
    return this.__givenName;
  }
  get familyName() {
    return this.__familyName;
  }
  update(update: IPersonParams) {
    // ignore the bug when undefined param is passed
    return new Person({
      givenName: update.givenName || this.__givenName,
      familyName: update.familyName || this.__familyName
    });
  }
}

It would be nice to see custom compilation errors immediately as it is done now with Visual Studio and Visual Studio Code, which run special tsc watch , not as part of webpack bundling or custom gulp task. There is an API for Typescript, but how to make it work seamless with tsc in VS/VS Code/Atom ?


Update with examples

The goal is just to write

@Record(Person)
interface IPerson {  
  givenName: string;
  familyName: string;
}
  • The class Person will be auto generated based on the interface IPerson as shown earlier.

  • It will be possible to instantiate the object:

    let instance = new Person({givenName: "Emma", familyName: "Watson"});

    Any incorrect property will raise a compilation error:

    let instance = new Person({nonExistedProperty: "Emma"}); //error

    Error: Property 'nonExistedProperty' does not exist in class Person constructor;
    Error: Property 'givenName' is required in class Person constructor;
    Error: Property 'familyName' is required in class Person constructor;

  • Existing object should be able to be partially updated

    let instance = new Person({givenName: "Emma", familyName: "Watson"}); instance.Update({givenName: "Luise"});

    instance.givenName === "Luise"; //TRUE;
    instance.familyName === "Watson"; //TRUE;

  • All properties are readonly

    let instance = new Person({givenName: "Emma", familyName: "Watson"}); instance.givenName = "John"; //error

    Error: Property 'givenName' is readonly;

  • Equals method is autogenerated. It can be based on hash or anything else, but should work fast and provide a deep check.

    let instance1 = new Person({givenName: "Emma", familyName: "Watson"});
    let instance2 = new Person({givenName: "Emma", familyName: "Watson"});

    instance1.Equals(instance2); //TRUE

    It might also have a place for controlling created instances and if a record with the same parameters exists in internal dictionary then it just returns the reference to this object:

    let instance1 = new Person({givenName: "Emma", familyName: "Watson"});
    let instance2 = new Person({givenName: "Emma", familyName: "Watson"});

    instance1 == instance2; //TRUE
    instance1 === instance2; //TRUE


  • Maybe instead of writing your own typescript (pre) processor, you could achieve your goals by using TypeScript decorators.

    This example is from https://www.typescriptlang.org/docs/handbook/decorators.html:

    @sealed
    class Greeter {
        greeting: string;
        constructor(message: string) {
            this.greeting = message;
        }
        greet() {
            return "Hello, " + this.greeting;
        }
    }
    
    function sealed(constructor: Function) {
        Object.seal(constructor);
        Object.seal(constructor.prototype);
    }
    

    This is what I've found so far. There is no simple way to do it at this moment (2017).

    One solution is to create a custom plugin that will use Typescript API. It will also run the services second time in its own sandbox alongside with TS services in VS Code , Atom or VS . In addition to this each IDE will require its own plugin to be created as a wrapper on top of your core plugin/service.

    In this way some people already made linters, for example, vscode-ng-language-service and ng2linter.

    Microsoft has #6508 ticket for TypeScript extensibility, which will make the requested features possible and simple to implement.


    For those who program in C# and F# it might be better to use the possibilities of Roslyn instead of waiting TypeScript extensions. A code written in C# can be transpiled to TypeScript or JavaScript. It also opens the wide possibilities for all kind of checking and custom modifications. And of course it is more close to DRY principle if there is the same logic in .NET and TypeScript/Javascript . Bridge.NET does not use Roslyn, but has a good implementation. Rosetta project for .NET 4 and Roslyn seems to be a good start.


    This is all possible without any compiler magic.

    external libs

    declare function someGenericEqualsFn(a, b): boolean;
    declare function makeCached<TResult, TFunc extends (...args) => TResult>(funcToCache: TFunc): TFunc;
    declare function merge<T>(objectToUpdate: T, objectToMerge: Partial<T>);
    

    our lib:

    interface DataRecord<TRecord> {
        equals<TRecord>(other: TRecord): boolean;
        update(update: Partial<TRecord>);
    }
    
    function createDataRecord<TRecord>(data: TRecord): Readonly<TRecord> & DataRecord<TRecord> {
        const result: TRecord & DataRecord<TRecord> = <any>{};
        Object.keys(data).forEach(() => {
    
        });
    
        result.equals = function (other: TRecord) {
            return someGenericEqualsFn(result, other);
        };
    
        result.update = function (partial: Partial<TRecord>) {
            merge(result, partial);
        };
    
        return result;
    }
    

    our test:

    interface IPerson {
        givenName: string;
        familyName: string;
    }
    
    let instance = createDataRecord<IPerson>({givenName: "Emma", familyName: "Watson"});
    instance = createDataRecord<IPerson>({nonExistedProperty: "Emma"}); // compiler error
    instance.givenName = "John";             // compiler error
    instance.update({givenName: "Emma"});     //   works!
    instance.update({nonExistedProperty: "x"});      // compiler error
    
    const createDataRecordOrGetCached = makeCached(createDataRecord);
    let instance1 = createDataRecordOrGetCached({givenName: "Emma", familyName: "Watson"});
    let instance2 = createDataRecordOrGetCached({givenName: "Emma", familyName: "Watson"});
    
    instance1 == instance2; //TRUE
    instance1 === instance2; //TRUE
    
    链接地址: http://www.djcxy.com/p/38154.html

    上一篇: 绘制训练,验证和测试集精度

    下一篇: Typescript自定义工具,转译器,扩展