Skip to content

Commit

Permalink
Add type source info for value type (#82)
Browse files Browse the repository at this point in the history
  • Loading branch information
zhuorantan authored Nov 8, 2021
1 parent 62ca184 commit fd220da
Show file tree
Hide file tree
Showing 11 changed files with 111 additions and 53 deletions.
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

0 comments on commit fd220da

Please sign in to comment.