/gwtscript

Primary LanguageTypeScriptMozilla Public License 2.0MPL-2.0

Hi there

This is GWTScript, tool for TS -> Java translation. It finds marked interfaces, type declarations and translates them into Java classes.

Development:

yarn start:dev

Table of content

Package usage guide

You should import compile() function and run it with config. Following code will find all files matching passed tsconfig, find all interfaces and types with "//@ToJava" comment and generate Java classes in "dist" folder.

import { compile } from '@dx-private/java-translate';
import { resolve } from 'path';

compile({
    tsconfigAbsolutePath: resolve(__dirname, './tsconfig.json'),
    destinationFolder: "dist",
    rootPackage: "com.devexperts.generated",
    generateFunctionType: (parameters, result) => {
        return {
            result: `JsFunc<${result}>`,
            imports: ["com.stuff.JsFunc"]
        }
    },
    generateArrayType: (type) => {
        return {
            imports: [],
            result: `Array<${type}>`
        }
    }
})

User Directives

The only way to interact with this lib from project code is directives. Directive is specific code comment with specific syntax. There are 3 directives:

  • Marking directive, "// @ToJava" by default. Tells translator to take interface or type declaration below and translate it.
  • Ignoring directive, "// @ToJavaIgnore" by default. Tells translator to ignore interface field below.
  • User Input directive, "// @InJava {JavaType} from {import1}, {import2}" by default. Tells translator to not parse interface field type and just take {JavaType} from directive
Marking directive

// @ToJava
interface A {
    a: number
    b: string
    c: boolean
}

// @ToJava 
type B = {
    c: {
        a: number
    }
}

// @ToJava
type C = Pick<A, "a" | "b">

Also you can override type name and package

// overriding name:
// @ToJava as B
interface A {
    a: number
    b: string
    c: boolean
}

// overriding package:
// @ToJava generated.js
// or
// @ToJava to generated.js
interface A {
    a: number
    b: string
    c: boolean
}

// both:
// @ToJava as B to generated.js
// or
// @ToJava to generated.js as B
// or
// @ToJava generated.js as B
// or
/* 
    @ToJava 
        as B
        to generated.js
*/
interface A {
    a: number
    b: string
    c: boolean
}

The provided package will be merged with specified root package and generated at the corresponding path.

For example:

// Source

// @ToJava as JsCarRed to generated.js.cars
interface RedCar {
    speed: number;
    color: string;
}

// @ToJava as JsCarGreen to generated.js.cars
interface GreenCar {
    speed: number;
    color: string;
}

// @ToJava as JsAirplane to generated.js.airplanes
interface Airplane {
    speed: number;
    color: string;
}
// Script

import { compile } from '@dx-private/java-translate';
import { resolve } from 'path';

compile({
    tsconfigAbsolutePath: resolve(__dirname, './tsconfig.json'),
    destinationFolder: "dist",
    rootPackage: "com.devexperts",
    generateFunctionType: (parameters, result) => {
        return {
            result: `JsFunc<${result}>`,
            imports: ["com.stuff.JsFunc"]
        }
    },
    generateArrayType: (type) => {
        return {
            imports: [],
            result: `Array<${type}>`
        }
    }
})

Result packages:

  • com.devexperts.generated.js.cars.jscarred
  • com.devexperts.generated.js.cars.jscargreen
  • com.devexperts.generated.js.airplanes.jsairplane

Result file structure:

  • dist
    • generated
      • js
        • cars
          • jscarred
            • JsCarRed.java
          • jscargreen
            • JsCarGreen.java
        • airplanes
          • jsairplane
            • JsAirplane.java
Ignoring directive

// @ToJava
interface A {
    a: number
    b: string
    c: boolean
    // @ToJavaIgnore
    d: SpecificType
}
User Input directive

// @ToJava
interface A {
    a: number
    // @InJava String
    b: string
    c: boolean
    // @InJava SimpleJavaType<int> from com.all.types.for.u.SimpleJavaType
    d: SpecificType<SpecificTypeA, SpecificTypeB>
    // @InJava MultiImportContainer<MultiImportContent> from com.types.lol.MultiImportContainer, com.dzeta.MultiImportContent
    e: UltraSpecificType
}

Config fields description

tsconfigAbsolutePath

Absolute path to project tsconfig file.

declare type tsconfigAbsolutePath = string;
interfacePredicate (Optional)(Default: /@ToJava/)

Filter (aka selector) (aka predicate) for interfaces and type declarations.

declare type interfacePredicate = 
    | RegExp
    | (node: ts.InterfaceDeclaration | ts.TypeAliasDeclaration) => boolean
filePredicate (Optional)

File filter.

declare type filePredicate = (fileName: string) => boolean
ignoreField (Optional)

Ignoring directive predicate function. true = ignore

declare type ignoreField = 
    | RegExp
    | (field: PropertySignature) => boolean
inJavaRegExpTest (Optional)

If provided replace "User Input directive" tag with passed RegExp

declare type inJavaRegExpTest = RegExp
destinationFolder

Generator destination folder

declare type destinationFolder = string
rootPackage

Root package for classes

declare type destinationFolder = string
generateFunctionType

Configures function field generation. Receives parameters and return type.

declare type generateFunctionType = (
        parameters: Array<{ name: string, type: string }>,
        returnType: string
    ) => {
        result: string;
        imports: string[];
    }

Example:

const generateFunctionType = (parameters: Array<{ name: string, type: string }>, returnType: string) => {
    if (
        returnType === "void"
    ) {
        return {
            result: `Action${
                parameters.length
            }<${parameters
                .map((param) => param.type)
                .join(", ")}>`,
            imports: [
                `com.github.timofeevda.gwt.rxjs.interop.functions.Action${parameters.length}`,
            ],
        };
    }
    return {
        result: `Func${
            parameters.length
        }<${func.parameters
            .map((param) => param.type)
            .join(", ")}, ${returnType}>`,
        imports: [
            `com.github.timofeevda.gwt.rxjs.interop.functions.Func${parameters.length}`,
        ],
    };
}
generateArrayType

Array type generator (same with generateFunctionType)

declare type generateArrayType = (
    type: string,
) => {
    result: string;
    imports: string[];
}

Example:

const generateArrayType = (type: string) => {
    return {
        imports: ["com.devexperts.client.reusable.core.list.JsList"],
        result: `JsList<${type}>`
    }
}
primitiveMapping (Optional)

Mapping for primitives telling generator how to translate NUMBER, STRING, BOOLEAN, ANY types. Example:

const primitiveMapping = {
    BOOLEAN: {
        result: "JsBool",
        imports: ["com.devexperts.client.reusable.core.JsBool"],
    },
    NUMBER: {
        result: "JsDouble",
        imports: ["com.devexperts.client.reusable.core.JsDouble"],
    },
    STRING: {
        result: "JsEnum",
        imports: ["com.devexperts.client.reusable.core.JsEnum"],
    },
    ANY: {
        result: "Object",
        imports: []
    }
}
nativeReferencesMap (Optional)

Mapping for types that should be converted in specific way. If you have type equivalents in both languages, rxjs Observable for example, you should enter this name in config. Example:

const nativeReferencesMap = {
    Bus: {
        import: "com.specific",
        text: "FEEventBus"
    },
    NewsSource: {
        import: "com.specific",
        text: "NewsSource"
    },
    Observable: {
        import: "com.rxjs.Observable",
        text: "Observable"
    }
}
getGroupName (Optional)

Sort generator output in folders by type info. Function should return folder name

interface TypeToGenerate {
    name: string;
    fields: readonly TypeField[];
    sourcePath: string;
}

declare type getGroupName = (type: TypeToGenerate) => string

Custom generator

This feature allows you to generate your own output instead of default Java GWT classes. Basically custom generator is just a function receiving parser output, so you can use it not only for code generation but just to take a look or experiments.

Usage

When you use a custom generator it is no longer necessary to provide basic generator config fields so the config type changes while you use a custom generator.

compile(
    {
        interfacePredicate: /@ToTranslate/,
        tsconfigAbsolutePath: resolve(__dirname, './tsconfig.json'),
    },
    (parsed: ParserOutput) => {
        // Our code
    }
)

Generator function should be passed as a second argument to compile() function and so you can use parser output. The types of nodes provided by Parser Output are described below.

interface ParserOutput {
    typesToGenerate: readonly TypeToGenerate[];
}

// Marked type
interface TypeToGenerate {
    name: string;
    fields: readonly TypeField[];
    sourcePath: string;
}

interface TypeField {
    name: string;
    type: ParsedType;
}

type ParsedType =
    | PrimitiveType
    | FunctionType
    | ObjectType
    | ArrayType
    | UnionType
    | ReferenceType
    | UserType
    | NumberLiteral
    | StringLiteral;

// Basic class for types
abstract class Type<ID, T> {
    public abstract identifier: ID;
    constructor(public type: T) {}
}

/*
    Literals like "string literal" or just 4
    type A = {
        stringLiteral: "str"
        numberLiteral: 4
    }
*/
class Literal<T extends number | string> extends Type<"literal", T> {
    identifier: "literal" = "literal";
}

class NumberLiteral extends Literal<number> {}

class StringLiteral extends Literal<string> {}

// parser only supports this primitives
type PrimitiveTypes = {
    NUMBER: true;
    STRING: true;
    BOOLEAN: true;
    VOID: true;
    ANY: true;
};

class PrimitiveType extends Type<"primitive", keyof PrimitiveTypes> {
    identifier: "primitive" = "primitive";
}

// "type" argument here is the return type
class FunctionType extends Type<"function", ParsedType> {
    identifier: "function" = "function";

    constructor(
        type: ParsedType,
        public parameters: ReadonlyArray<{ name: string; type: ParsedType }>
    ) {
        super(type);
    }
}

class ObjectType extends Type<
    "object",
    ReadonlyArray<{ name: string; type: ParsedType }>
> {
    identifier: "object" = "object";
    constructor(
        value: ReadonlyArray<{ name: string; type: ParsedType }>
    ) {
        super(value);
    }
}

class ArrayType extends Type<"array", ParsedType> {
    identifier: "array" = "array";
}


class UnionType<T extends ParsedType = ParsedType> extends Type<
    "union",
    readonly T[]
> {
    identifier: "union" = "union";
}


class ReferenceType extends Type<
    "reference",
    { typeName: string; genericArgs: readonly ParsedType[] }
> {
    identifier: "reference" = "reference";
}

// The type you get when use User Input directive
class UserType extends Type<
    "hard-coded-type",
    { text: string; imports: string[] }
> {
    identifier: "hard-coded-type" = "hard-coded-type";
}