2017-01-28 57 views
7

Chciałbym zmienić/sprawdzić pliki .ts, zanim tsc rozpocznie proces transpozycji, podobny do tego, co Roslyn zapewnia C#.Niestandardowe narzędzie maszynowe, transpilator, rozszerzenie

Jest to przydatne do sprawdzania typu statycznego. Na przykład, odpowiednie i krótkie Immutable Record realizacja wymaga Kod zmieniania/statyczne sprawdzanie w czasie kompilacji, jak można to zrobić z Flow:

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

a następnie zwyczaj TSC transpiler mógłby zmodyfikować kod do:

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 
    }); 
    } 
} 

Byłoby miło zobaczyć natychmiastowe błędy kompilacji niestandardowej, ponieważ teraz jest to robione przy użyciu Visual Studio i Visual Studio Code, które uruchamiają specjalne tsc watch, a nie jako część pakowania pakietów internetowych lub niestandardowych zadań gulp. Jest API dla Typescript, ale jak sprawić, by działał bezproblemowo z tsc w VS/VS Code/Atom?


Aktualizacja z przykładów

Celem jest po prostu napisać

@Record(Person) 
interface IPerson { 
    givenName: string; 
    familyName: string; 
} 
  1. Klasa Person będą automatycznie generowane na podstawie interfejsu IPerson jak pokazano wcześniej.

  2. Będzie to możliwe do wystąpienia obiektu:

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

    Wszelkie nieprawidłowe nieruchomość podniesie błąd kompilacji:

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

    Błąd: własność 'nonExistedProperty' nie istnieje w klasie Osoba konstruktor;
    Błąd: Właściwość 'givenName' jest wymagana w klasie Osoba konstruktor;
    Błąd: Właściwość "familyName" jest wymagana w klasie Osoba konstruktor;

  3. Istniejący obiekt powinien móc być częściowo aktualizowana

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

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

  4. Wszystkie właściwości są tylko do odczytu

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

    Błąd: właściwość "givenName" jest tylko do odczytu;

  5. Equals metoda jest autogenerowana.Może być oparty na haszyszu lub cokolwiek innego, ale powinien działać szybko i zapewniać dokładną kontrolę.

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

    instance1.Equals(instance2); //TRUE

    Może także mieć miejsce do sterowania utworzonych kopii i jeśli rekord z tymi samymi parametrami istnieje w wewnętrznym słowniku to właśnie zwraca referencję do tego obiektu:

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

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

+0

mógłbyś podać jedną lub więcej przykłady użycia? Jak wyglądałby kod użytkownika, gdy dekorator zostanie zinterpretowany tak, jak sobie wyobrażasz? Co byłoby możliwe, co byłoby ograniczone? – Benjamin

+0

Właśnie przypomniałem sobie, że nie można opisywać interfejsów w TypeScripcie. Obejściem tutaj jest użycie klas zamiast interfejsów: 'interface A {} ... class B implementuje A {}' – Benjamin

+0

@Benjamin problem jest głębszy niż adnotacje interfejsu. Kompilator nie może w pełni sprawdzić dynamicznie tworzonych obiektów. Adnotacje maszynopisu dopuszczają tylko obiekty tworzone dynamicznie, ale nie statyczne. Dlatego potrzebne jest niestandardowe narzędzie kompilacji pośredniej, które może tworzyć klasy (statyczne), które będą dodatkowo sprawdzane przez kompilator. – Artru

Odpowiedz

1

Może zamiast pisania własnych maszynopis (pre) procesor, można osiągnąć swoje cele używając dekoratorów maszynopis.

Ten przykład jest od 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); 
} 
+0

Dziękuję za sugestię. Była to moja pierwsza próba sprawdzenia, ale jest problem: aby poprawnie zrozumieć strukturę obiektu, kod powinien być gotowy do analizy statycznej. Oznacza to, że tworząc dynamiczne obiekty, takie jak "obj [someVarProp] = propValue" kompilator nie może zrozumieć i sprawdzić poprawności kodu. Tak więc ta procedura powinna być ściśle powiązana z kompilatorem, jak to jest możliwe w projekcie Roslyn. – Artru

1

To co znalazłem do tej pory. Nie ma prostego sposobu, aby to zrobić w tej chwili (2017).

Jednym z rozwiązań jest stworzenie niestandardowej wtyczki, która będzie korzystać z API typu maszynopis. Usługa będzie również uruchamiać usługi po raz drugi w swojej własnej piaskownicy wraz z usługami TS w VS Code, Atom lub VS. Oprócz tego każdy IDE będzie wymagał utworzenia własnej wtyczki jako opakowania na rdzeń wtyczki/usługi.

W ten sposób niektórzy już utworzyli linters, na przykład vscode-ng-language-service i ng2linter.

Microsoft ma bilet #6508 dla rozszerzalności TypeScript, dzięki czemu żądane funkcje są możliwe i łatwe do wdrożenia.


Dla tych, którzy programować w C# i F# może lepiej wykorzystać możliwości Roslyn zamiast czekać rozszerzeń maszynopis. Kod napisany w języku C# można przenosić do TypeScript lub JavaScript. Otwiera także szerokie możliwości wszelkiego rodzaju sprawdzania i niestandardowych modyfikacji. I oczywiście jest to bardziej zbliżone do DRY principle, jeśli jest taka sama logika w .NET i TypeScript/Javascript. Bridge.NET nie używa Roslyn, ale ma dobrą implementację. Rosetta Projekt .NET 4 i Roslyn wydaje się być dobrym początkiem.

1

Jest to możliwe bez żadnej magii kompilatora.

libs zewnętrzne

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>); 

nasz 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; 
} 

nasz 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 
+0

To nie odpowiada na ogólne pytanie, ale rozwiązuje jeden z moich dużych problemów projektowych. Bardzo dziękuję @Benjamin za pomoc! Bardzo, bardzo przydatny! Zmapowane typy rzeczywiście rozwiązują problem z polami częściowej aktualizacji i tylko do odczytu. – Artru