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

Add type source info for value type #82

Merged
merged 3 commits into from
Nov 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ts-gyb",
"version": "0.6.2",
"version": "0.6.3",
"description": "Generate Native API based on TS interface",
"repository": {
"type": "git",
Expand Down
16 changes: 8 additions & 8 deletions src/cli/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface ParseConfiguration {
/**
* Scoped source file paths. The key is the scope name and the value is an array of the source file paths. [Glob patterns](https://en.wikipedia.org/wiki/Glob_(programming)) are allowed.
* If it is a relative path, it will be resolved based on the configuration file path.
*
*
* For example, `{ "api": ["src/api/IEditor.ts", "src/bridge/*.ts"] }`
*/
source: Record<string, string[]>;
Expand All @@ -17,7 +17,7 @@ export interface ParseConfiguration {
*/
predefinedTypes?: string[];
/**
* Custom tags for code generation in mustache and its default value.
* Custom tags for code generation in mustache and its default value.
*/
defaultCustomTags?: Record<string, unknown>;
/**
Expand All @@ -42,16 +42,16 @@ export interface RenderConfiguration {
templates: Record<string, string>;
/**
* Scoped output directories or paths. The key is the scope name and the value is the output directory or file path.
*
*
* If it is a relative path, it will be resolved based on the configuration file path.
*
*
* For example, `{ "api": "../ios/AppTarget/Generated" }`
*/
outputPath: Record<string, string>;
/**
* Template path for named types. Must be a mustache template.
* If it is a relative path, it will be resolved based on the configuration file path.
*
*
* For example, `code-templates/named-types.mustache`.
*/
namedTypesTemplatePath: string;
Expand All @@ -62,7 +62,7 @@ export interface RenderConfiguration {
namedTypesOutputPath: string;
/**
* The mapping from `predefinedTypes` to the existing types in the target language (Kotlin/Swift).
*
*
* For example, `{ "CodeGen_Int": "Int" }`.
*/
typeNameMap?: Record<string, string>;
Expand Down Expand Up @@ -107,11 +107,11 @@ function normalizeRenderConfiguration(basePath: string, config?: RenderConfigura
namedTypesOutputPath = normalizePath(namedTypesOutputPath, basePath);
namedTypesTemplatePath = normalizePath(namedTypesTemplatePath, basePath);

Object.keys(templates).forEach(key => {
Object.keys(templates).forEach((key) => {
templates[key] = normalizePath(templates[key], basePath);
});

Object.keys(outputPath).forEach(key => {
Object.keys(outputPath).forEach((key) => {
outputPath[key] = normalizePath(outputPath[key], basePath);
});

Expand Down
31 changes: 22 additions & 9 deletions src/generator/CodeGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fs from 'fs';
import path from 'path';
import { dropIPrefixInCustomTypes, fetchNamedTypes, NamedType, NamedTypesResult } from './named-types';
import { dropIPrefixInCustomTypes, fetchNamedTypes, NamedTypeInfo, NamedTypesResult } from './named-types';
import { Parser } from '../parser/Parser';
import { renderCode } from '../renderer/renderer';
import { NamedTypeView, ModuleView, InterfaceTypeView, EnumTypeView } from '../renderer/views';
Expand Down Expand Up @@ -58,7 +58,14 @@ export class CodeGenerator {

console.log('Modules:\n');
console.log(
modules.map((module) => serializeModule(module, this.namedTypes?.associatedTypes[module.name] ?? [])).join('\n\n')
modules
.map((module) =>
serializeModule(
module,
(this.namedTypes?.associatedTypes[module.name] ?? []).map((associatedType) => associatedType.type)
)
)
.join('\n\n')
);
console.log();
}
Expand All @@ -69,7 +76,7 @@ export class CodeGenerator {
}

console.log('Shared named types:\n');
console.log(this.namedTypes.sharedTypes.map((namedType) => serializeNamedType(namedType)).join('\n\n'));
console.log(this.namedTypes.sharedTypes.map((namedType) => serializeNamedType(namedType.type)).join('\n\n'));
}

renderModules({
Expand All @@ -96,7 +103,9 @@ export class CodeGenerator {
const { associatedTypes } = this.namedTypes;
const valueTransformer = this.getValueTransformer(language, typeNameMap);

const moduleViews = modules.map((module) => this.getModuleView(module, associatedTypes[module.name] ?? [], valueTransformer));
const moduleViews = modules.map((module) =>
this.getModuleView(module, associatedTypes[module.name] ?? [], valueTransformer)
);

if (path.extname(outputPath) === '') {
// The path is a directory
Expand Down Expand Up @@ -154,20 +163,24 @@ export class CodeGenerator {
}
}

private getNamedTypeView(namedType: NamedType, valueTransformer: ValueTransformer): NamedTypeView {
private getNamedTypeView(namedType: NamedTypeInfo, valueTransformer: ValueTransformer): NamedTypeView {
let namedTypeView: NamedTypeView;
if (isInterfaceType(namedType)) {
namedTypeView = new InterfaceTypeView(namedType, valueTransformer);
if (isInterfaceType(namedType.type)) {
namedTypeView = new InterfaceTypeView(namedType.type, namedType.source, valueTransformer);
namedTypeView.custom = true;
} else {
namedTypeView = new EnumTypeView(namedType, valueTransformer);
namedTypeView = new EnumTypeView(namedType.type, namedType.source, valueTransformer);
namedTypeView.enum = true;
}

return namedTypeView;
}

private getModuleView(module: Module, associatedTypes: NamedType[], valueTransformer: ValueTransformer): ModuleView {
private getModuleView(
module: Module,
associatedTypes: NamedTypeInfo[],
valueTransformer: ValueTransformer
): ModuleView {
return new ModuleView(
module,
associatedTypes.map((associatedType) => this.getNamedTypeView(associatedType, valueTransformer)),
Expand Down
48 changes: 33 additions & 15 deletions src/generator/named-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,24 @@ import {
ValueTypeKind,
} from '../types';

export const enum ValueTypeSource {
Field = 1 << 0,
Parameter = 1 << 1,
Return = 1 << 2,
}

export type NamedType = InterfaceType | EnumType;
export type NamedTypesResult = { associatedTypes: Record<string, NamedType[]>; sharedTypes: NamedType[] };
export interface NamedTypeInfo {
type: NamedType;
source: ValueTypeSource;
}

export type NamedTypesResult = { associatedTypes: Record<string, NamedTypeInfo[]>; sharedTypes: NamedTypeInfo[] };

export function dropIPrefixInCustomTypes(modules: Module[]): void {
modules
.flatMap((module) => fetchRootTypes(module))
.forEach((valueType) => {
.forEach(([valueType]) => {
recursiveVisitMembersType(valueType, (namedType) => {
if (!isInterfaceType(namedType)) {
return;
Expand All @@ -32,10 +43,10 @@ export function dropIPrefixInCustomTypes(modules: Module[]): void {
}

export function fetchNamedTypes(modules: Module[]): NamedTypesResult {
const typeMap: Record<string, { namedType: NamedType; associatedModules: Set<string> }> = {};
const typeMap: Record<string, { namedType: NamedType; source: ValueTypeSource; associatedModules: Set<string> }> = {};

modules.forEach((module) => {
fetchRootTypes(module).forEach((valueType) => {
fetchRootTypes(module).forEach(([valueType, source]) => {
recursiveVisitMembersType(valueType, (membersType, path) => {
let namedType = membersType;
if (isTupleType(namedType)) {
Expand All @@ -47,37 +58,44 @@ export function fetchNamedTypes(modules: Module[]): NamedTypesResult {
}

if (typeMap[namedType.name] === undefined) {
typeMap[namedType.name] = { namedType, associatedModules: new Set() };
typeMap[namedType.name] = { namedType, source, associatedModules: new Set() };
}

typeMap[namedType.name].associatedModules.add(module.name);
const existingResult = typeMap[namedType.name];
existingResult.associatedModules.add(module.name);
existingResult.source |= source;
});
});
});

const associatedTypes: Record<string, NamedType[]> = {};
const sharedTypes: NamedType[] = [];
const associatedTypes: Record<string, NamedTypeInfo[]> = {};
const sharedTypes: NamedTypeInfo[] = [];

Object.values(typeMap).forEach(({ namedType, associatedModules }) => {
Object.values(typeMap).forEach(({ namedType, source, associatedModules }) => {
if (associatedModules.size === 1) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const moduleName: string = associatedModules.values().next().value;
if (associatedTypes[moduleName] === undefined) {
associatedTypes[moduleName] = [];
}
associatedTypes[moduleName].push(namedType);
associatedTypes[moduleName].push({ type: namedType, source });
} else {
sharedTypes.push(namedType);
sharedTypes.push({ type: namedType, source });
}
});

return { associatedTypes, sharedTypes };
}

function fetchRootTypes(module: Module): ValueType[] {
const typesInMembers = module.members.map((field) => field.type);
const typesInMethods = module.methods.flatMap((method) =>
method.parameters.map((parameter) => parameter.type).concat(method.returnType ? [method.returnType] : [])
function fetchRootTypes(module: Module): [ValueType, ValueTypeSource][] {
const typesInMembers: [ValueType, ValueTypeSource][] = module.members.map((field) => [
field.type,
ValueTypeSource.Field,
]);
const typesInMethods: [ValueType, ValueTypeSource][] = module.methods.flatMap((method) =>
method.parameters
.map((parameter): [ValueType, ValueTypeSource] => [parameter.type, ValueTypeSource.Parameter])
.concat(method.returnType ? [[method.returnType, ValueTypeSource.Return]] : [])
);

return typesInMembers.concat(typesInMethods);
Expand Down
18 changes: 11 additions & 7 deletions src/parser/Parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,15 @@ export class Parser {
const members: Field[] = [];
const methods: Method[] = [];

node.members.forEach(methodNode => {
node.members.forEach((methodNode) => {
try {
if (ts.isPropertySignature(methodNode)) {
if (methodNode.type !== undefined && ts.isFunctionTypeNode(methodNode.type)) {
const method = this.methodFromNode({ ...methodNode, type: methodNode.type });
if (method !== null) {
methods.push(method);
}
}
else {
} else {
const field = this.valueParser.fieldFromTypeElement(methodNode);
if (field !== null) {
members.push(field);
Expand All @@ -88,7 +87,11 @@ export class Parser {
methods.push(method);
}
} else {
throw new ParserError(node, 'it is not valid property signature or method signature', 'Please define only properties or methods');
throw new ParserError(
node,
'it is not valid property signature or method signature',
'Please define only properties or methods'
);
}
} catch (error) {
if (error instanceof ParserError) {
Expand All @@ -114,14 +117,15 @@ export class Parser {
};
}

private methodFromNode(node: ts.MethodSignature | ts.PropertySignature & { type: ts.SignatureDeclarationBase }): Method | null {
private methodFromNode(
node: ts.MethodSignature | (ts.PropertySignature & { type: ts.SignatureDeclarationBase })
): Method | null {
const methodName = node.name.getText();

let signatureNode: ts.SignatureDeclarationBase;
if (ts.isMethodSignature(node)) {
signatureNode = node;
}
else {
} else {
signatureNode = node.type;
}

Expand Down
11 changes: 6 additions & 5 deletions src/parser/ValueParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ export class ValueParser {
return dictionaryType;
}

const fields = this.checker.getPropertiesOfType(this.checker.getTypeAtLocation(typeNode))
const fields = this.checker
.getPropertiesOfType(this.checker.getTypeAtLocation(typeNode))
.map((symbol) => symbol.valueDeclaration)
.filter((declaration): declaration is ts.Declaration => declaration !== undefined)
.map((declaration) => this.fieldFromTypeElement(declaration))
Expand Down Expand Up @@ -414,7 +415,8 @@ export class ValueParser {

const name = node.name.getText();

const members = this.checker.getPropertiesOfType(this.checker.getTypeAtLocation(node))
const members = this.checker
.getPropertiesOfType(this.checker.getTypeAtLocation(node))
.map((symbol) => symbol.valueDeclaration)
.filter((declaration): declaration is ts.Declaration => declaration !== undefined)
.map((declaration) => this.fieldFromTypeElement(declaration))
Expand Down Expand Up @@ -532,7 +534,7 @@ export class ValueParser {
if (!isBasicType(keyType)) {
throw Error(`Key type kind ${keyType.kind} is not supported as key for dictionary`);
}

let dictKey: DictionaryKeyType;
if (keyType.value === BasicTypeValue.string) {
dictKey = DictionaryKeyType.string;
Expand Down Expand Up @@ -584,8 +586,7 @@ export class ValueParser {
let referencedNode: ts.Declaration;
try {
referencedNode = this.getReferencedTypeNode(typeNode);
}
catch {
} catch {
return null;
}

Expand Down
1 change: 0 additions & 1 deletion src/renderer/value-transformer/SwiftValueTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,5 +144,4 @@ export class SwiftValueTransformer implements ValueTransformer {
convertTypeNameFromCustomMap(name: string): string {
return this.typeNameMap[name] ?? name;
}

}
15 changes: 14 additions & 1 deletion src/renderer/views/EnumTypeView.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { ValueTypeSource } from '../../generator/named-types';
import { EnumSubType, EnumType } from '../../types';
import { getDocumentationLines } from '../utils';
import { ValueTransformer } from '../value-transformer';

export class EnumTypeView {
constructor(private enumType: EnumType, private valueTransformer: ValueTransformer) {}
constructor(
private readonly enumType: EnumType,
private readonly source: ValueTypeSource,
private readonly valueTransformer: ValueTransformer
) {}

get typeName(): string {
return this.valueTransformer.convertTypeNameFromCustomMap(this.enumType.name);
Expand Down Expand Up @@ -46,4 +51,12 @@ export class EnumTypeView {
get customTags(): Record<string, unknown> {
return this.enumType.customTags;
}

get isFromParameter(): boolean {
return (this.source & ValueTypeSource.Parameter) === ValueTypeSource.Parameter;
}

get isFromReturn(): boolean {
return (this.source & ValueTypeSource.Return) === ValueTypeSource.Return;
}
}
10 changes: 10 additions & 0 deletions src/renderer/views/InterfaceTypeView.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { ValueTypeSource } from '../../generator/named-types';
import { InterfaceType } from '../../types';
import { getDocumentationLines } from '../utils';
import { ValueTransformer } from '../value-transformer';

export class InterfaceTypeView {
constructor(
private readonly interfaceType: InterfaceType,
private readonly source: ValueTypeSource,
private readonly valueTransformer: ValueTransformer
) {}

Expand Down Expand Up @@ -47,4 +49,12 @@ export class InterfaceTypeView {
get customTags(): Record<string, unknown> {
return this.interfaceType.customTags;
}

get isFromParameter(): boolean {
return (this.source & ValueTypeSource.Parameter) === ValueTypeSource.Parameter;
}

get isFromReturn(): boolean {
return (this.source & ValueTypeSource.Return) === ValueTypeSource.Return;
}
}
Loading