зеркало из https://github.com/microsoft/ts-gyb.git
Parse module associated named types and generate in modules (#16)
* Parse module associated named types * Generate associated types in module * Lint * Serialize associated types * Lint * Support multiple module paths * Fix demo.ts * Modify template * Improve print * Lower cased enum * Uncapitalize enum * Update template * Remove gulp * Cleanup
This commit is contained in:
Родитель
a7c6cc4a9c
Коммит
8437bf3d95
|
@ -2,6 +2,7 @@
|
|||
// Copyright 2013-2018 Microsoft Inc.
|
||||
//
|
||||
|
||||
// swiftformat:disable redundantRawValues
|
||||
// Don't modify this file manually, it's auto generated.
|
||||
|
||||
public class {{moduleName}} {
|
||||
|
@ -34,3 +35,7 @@ public class {{moduleName}} {
|
|||
}
|
||||
{{/methods}}
|
||||
}
|
||||
{{#associatedTypes}}
|
||||
|
||||
{{> swift-named-type}}
|
||||
{{/associatedTypes}}
|
|
@ -1,13 +1,4 @@
|
|||
//
|
||||
// Copyright 2013-2018 Microsoft Inc.
|
||||
//
|
||||
|
||||
// swiftformat:disable redundantRawValues
|
||||
// Don't modify this file manually, it's auto generated.
|
||||
|
||||
import UIKit
|
||||
{{#customTypes}}
|
||||
|
||||
{{#custom}}
|
||||
public struct {{typeName}}: Codable {
|
||||
{{#members}}
|
||||
public var {{name}}: {{type}}
|
||||
|
@ -19,12 +10,11 @@ public struct {{typeName}}: Codable {
|
|||
{{/members}}
|
||||
}
|
||||
}
|
||||
{{/customTypes}}
|
||||
{{#enumTypes}}
|
||||
|
||||
{{/custom}}
|
||||
{{#enum}}
|
||||
public enum {{typeName}}: {{valueType}}, Codable {
|
||||
{{#members}}
|
||||
case {{key}} = {{{value}}}
|
||||
{{/members}}
|
||||
}
|
||||
{{/enumTypes}}
|
||||
{{/enum}}
|
|
@ -0,0 +1,12 @@
|
|||
//
|
||||
// Copyright 2013-2018 Microsoft Inc.
|
||||
//
|
||||
|
||||
// swiftformat:disable redundantRawValues
|
||||
// Don't modify this file manually, it's auto generated.
|
||||
|
||||
import UIKit
|
||||
{{#.}}
|
||||
|
||||
{{> swift-named-type}}
|
||||
{{/.}}
|
|
@ -2,6 +2,8 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftformat:disable redundantRawValues
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol {{moduleName}}: EditorNativeModule {
|
||||
|
@ -46,6 +48,7 @@ class {{customTags.bridgeName}}: NativeModuleBridge {
|
|||
parameters = try decoder.decode(Parameters.self, from: parametersData)
|
||||
}
|
||||
catch {
|
||||
logAssertFail("Parameters of {{methodName}} are invalid: \(error)")
|
||||
completion(.failure(NativeMethodError.invalidParameters(parametersData)))
|
||||
return
|
||||
}
|
||||
|
@ -57,3 +60,7 @@ class {{customTags.bridgeName}}: NativeModuleBridge {
|
|||
}
|
||||
{{/methods}}
|
||||
}
|
||||
{{#associatedTypes}}
|
||||
|
||||
{{> swift-named-type}}
|
||||
{{/associatedTypes}}
|
36
gulpfile.ts
36
gulpfile.ts
|
@ -1,36 +0,0 @@
|
|||
import {series, watch, dest} from 'gulp'
|
||||
import ts from 'gulp-typescript'
|
||||
import del from 'del'
|
||||
import {spawn} from 'child_process'
|
||||
|
||||
const tsProject = ts.createProject("tsconfig.json", {declaration: true})
|
||||
|
||||
function tsBuild() {
|
||||
return tsProject
|
||||
.src()
|
||||
.pipe(tsProject())
|
||||
.pipe(dest("dist"))
|
||||
}
|
||||
|
||||
async function run() {
|
||||
spawn('node', ['dist/index.js'], {stdio: 'inherit'})
|
||||
}
|
||||
|
||||
async function tsRun() {
|
||||
spawn('ts-node', ['src/index.ts'], {stdio: 'inherit'})
|
||||
}
|
||||
|
||||
function watchRun() {
|
||||
watch('src/*.ts', tsRun)
|
||||
}
|
||||
|
||||
export const build = tsBuild
|
||||
|
||||
export function clean() {
|
||||
return del('dist')
|
||||
}
|
||||
|
||||
export const start = series(clean, build, run)
|
||||
|
||||
export const dev = series(tsRun, watchRun)
|
||||
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
11
package.json
11
package.json
|
@ -3,14 +3,12 @@
|
|||
"version": "0.1.11",
|
||||
"description": "Generate Native API based on TS interface",
|
||||
"scripts": {
|
||||
"build": "gulp build",
|
||||
"clean": "gulp clean",
|
||||
"start": "gulp start",
|
||||
"dev": "gulp dev",
|
||||
"build": "tsc",
|
||||
"clean": "rm -rf dist",
|
||||
"debug": "ts-node ./src/index",
|
||||
"start:example": "ts-node ./src/demo/demo",
|
||||
"test": "mocha -r ts-node/register test/**/*.ts",
|
||||
"lint": "eslint ./src --ext .js,.jsx,.ts,.tsx",
|
||||
"lint": "eslint ./src --ext .js,.ts",
|
||||
"prettier:write": "prettier --write \"src/**/*.ts\"",
|
||||
"prettier:check": "prettier --check \"src/**/*.ts\"",
|
||||
"lint:fix": "npm run lint -- --fix && npm run prettier:write",
|
||||
|
@ -30,7 +28,6 @@
|
|||
"license": "private",
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.2.18",
|
||||
"@types/gulp": "^4.0.6",
|
||||
"@types/mocha": "^8.2.2",
|
||||
"@types/mustache": "^4.1.1",
|
||||
"@types/sinon": "^10.0.1",
|
||||
|
@ -45,8 +42,6 @@
|
|||
"eslint-config-airbnb-base": "^14.2.1",
|
||||
"eslint-config-prettier": "^7.0.0",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-typescript": "^6.0.0-alpha.1",
|
||||
"mocha": "^8.4.0",
|
||||
"prettier": "^2.2.1",
|
||||
"sinon": "^11.1.1",
|
||||
|
|
|
@ -2,13 +2,16 @@ import { CodeGenerator, RenderingLanguage } from '../generator/CodeGenerator';
|
|||
|
||||
function run(): void {
|
||||
const generator = new CodeGenerator();
|
||||
generator.parse({ tag: 'APIs', interfacePaths: ['src/demo/data/demoApi.ts'], defaultCustomTags: {}, dropInterfaceIPrefix: true });
|
||||
generator.render({
|
||||
tag: 'APIs',
|
||||
generator.parse({
|
||||
interfacePaths: ['src/demo/data/demoApi.ts'],
|
||||
defaultCustomTags: {},
|
||||
dropInterfaceIPrefix: true,
|
||||
});
|
||||
generator.renderModules({
|
||||
index: 0,
|
||||
language: RenderingLanguage.Swift,
|
||||
outputDirectory: 'generated',
|
||||
moduleTemplatePath: 'templates/swift-bridge.mustache',
|
||||
namedTypesTemplatePath: 'templates/swift-named-types.mustache',
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { dropIPrefixInCustomTypes, fetchNamedTypes, NamedType } from './named-types';
|
||||
import { dropIPrefixInCustomTypes, fetchNamedTypes, NamedType, NamedTypesResult } from './named-types';
|
||||
import { Parser } from '../parser/Parser';
|
||||
import { renderCode } from '../renderer/renderer';
|
||||
import { SwiftCustomTypeView } from '../renderer/swift/SwiftCustomTypeView';
|
||||
import { SwiftEnumTypeView } from '../renderer/swift/SwiftEnumTypeView';
|
||||
import { SwiftModuleView } from '../renderer/swift/SwiftModuleView';
|
||||
import { CustomTypeView, EnumTypeView, ModuleView, NamedTypesView } from '../renderer/views';
|
||||
import { CustomTypeView, EnumTypeView, ModuleView, NamedTypeView } from '../renderer/views';
|
||||
import { serializeModule, serializeNamedType } from '../serializers';
|
||||
import { CustomType, EnumType, isCustomType, Module } from '../types';
|
||||
import { applyDefaultCustomTags } from './utils';
|
||||
|
@ -16,20 +16,18 @@ export enum RenderingLanguage {
|
|||
}
|
||||
|
||||
export class CodeGenerator {
|
||||
private modulesMap: Record<string, Module[]> = {};
|
||||
private modulesMap: Module[][] = [];
|
||||
|
||||
private namedTypes: Record<string, NamedType> = {};
|
||||
private namedTypes?: NamedTypesResult;
|
||||
|
||||
parse({
|
||||
tag,
|
||||
interfacePaths,
|
||||
defaultCustomTags,
|
||||
dropInterfaceIPrefix,
|
||||
}: {
|
||||
tag: string;
|
||||
interfacePaths: string[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
defaultCustomTags: Record<string, any>,
|
||||
defaultCustomTags: Record<string, any>;
|
||||
dropInterfaceIPrefix: boolean;
|
||||
}): void {
|
||||
const parser = new Parser(interfacePaths);
|
||||
|
@ -41,59 +39,80 @@ export class CodeGenerator {
|
|||
dropIPrefixInCustomTypes(modules);
|
||||
}
|
||||
|
||||
const namedTypes = fetchNamedTypes(modules);
|
||||
|
||||
this.modulesMap[tag] = modules;
|
||||
this.pushNamedTypes(namedTypes);
|
||||
this.modulesMap.push(modules);
|
||||
}
|
||||
|
||||
printModules({ tag }: { tag: string }): void {
|
||||
const modules = this.modulesMap[tag];
|
||||
parseNamedTypes(): void {
|
||||
this.namedTypes = fetchNamedTypes(Object.values(this.modulesMap).flatMap((modules) => modules));
|
||||
}
|
||||
|
||||
printModules(index: number): void {
|
||||
const modules = this.modulesMap[index];
|
||||
if (modules === undefined) {
|
||||
throw Error('Modules not parsed. Run parse first.');
|
||||
}
|
||||
|
||||
console.log('Modules:\n');
|
||||
console.log(modules.map((module) => serializeModule(module)).join('\n\n'));
|
||||
}
|
||||
|
||||
printNamedTypes(): void {
|
||||
console.log('\nNamed types:\n');
|
||||
console.log(
|
||||
Object.entries(this.namedTypes)
|
||||
.map(([typeName, namedType]) => serializeNamedType(typeName, namedType))
|
||||
.join('\n\n')
|
||||
modules.map((module) => serializeModule(module, this.namedTypes?.associatedTypes[module.name] ?? [])).join('\n\n')
|
||||
);
|
||||
console.log();
|
||||
}
|
||||
|
||||
render({
|
||||
tag,
|
||||
printSharedNamedTypes(): void {
|
||||
if (this.namedTypes === undefined) {
|
||||
throw Error('Named types not parsed. Run parseNamedTypes first.');
|
||||
}
|
||||
|
||||
console.log('Shared named types:\n');
|
||||
console.log(this.namedTypes.sharedTypes.map((namedType) => serializeNamedType(namedType)).join('\n\n'));
|
||||
}
|
||||
|
||||
renderModules({
|
||||
index,
|
||||
language,
|
||||
outputDirectory,
|
||||
moduleTemplatePath,
|
||||
namedTypesTemplatePath,
|
||||
}: {
|
||||
tag: string;
|
||||
index: number;
|
||||
language: RenderingLanguage;
|
||||
outputDirectory: string;
|
||||
moduleTemplatePath: string;
|
||||
namedTypesTemplatePath: string;
|
||||
}): void {
|
||||
const modules = this.modulesMap[tag];
|
||||
const modules = this.modulesMap[index];
|
||||
if (modules === undefined) {
|
||||
throw Error('Modules not parsed. Run parse first.');
|
||||
}
|
||||
if (this.namedTypes === undefined) {
|
||||
throw Error('Named types not parsed. Run parseNamedTypes first.');
|
||||
}
|
||||
|
||||
const { associatedTypes } = this.namedTypes;
|
||||
|
||||
modules.forEach((module) => {
|
||||
const moduleView = this.getModuleView(language, module);
|
||||
const moduleView = this.getModuleView(language, module, associatedTypes[module.name] ?? []);
|
||||
const renderedCode = renderCode(moduleTemplatePath, moduleView);
|
||||
|
||||
this.writeFile(renderedCode, outputDirectory, `${moduleView.moduleName}${this.getFileExtension(language)}`);
|
||||
});
|
||||
}
|
||||
|
||||
const namedTypesView = this.getNamedTypesView(language, this.namedTypes);
|
||||
renderNamedTypes({
|
||||
language,
|
||||
namedTypesTemplatePath,
|
||||
namedTypesOutputPath,
|
||||
}: {
|
||||
language: RenderingLanguage;
|
||||
namedTypesTemplatePath: string;
|
||||
namedTypesOutputPath: string;
|
||||
}): void {
|
||||
if (this.namedTypes === undefined) {
|
||||
throw Error('Named types not parsed. Run parseNamedTypes first.');
|
||||
}
|
||||
|
||||
const namedTypesView = this.namedTypes.sharedTypes.map((namedType) => this.getNamedTypeView(language, namedType));
|
||||
const renderedCode = renderCode(namedTypesTemplatePath, namedTypesView);
|
||||
this.writeFile(renderedCode, outputDirectory, `Generated_CustomInterface${this.getFileExtension(language)}`);
|
||||
fs.writeFileSync(namedTypesOutputPath, renderedCode);
|
||||
}
|
||||
|
||||
private getFileExtension(language: RenderingLanguage): string {
|
||||
|
@ -105,24 +124,26 @@ export class CodeGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
private getNamedTypesView(language: RenderingLanguage, namedTypes: Record<string, NamedType>): NamedTypesView {
|
||||
const namedTypesView: NamedTypesView = { customTypes: [], enumTypes: [] };
|
||||
private getNamedTypeView(language: RenderingLanguage, namedType: NamedType): NamedTypeView {
|
||||
let namedTypeView: NamedTypeView;
|
||||
if (isCustomType(namedType)) {
|
||||
namedTypeView = this.getCustomTypeView(language, namedType.name, namedType);
|
||||
namedTypeView.custom = true;
|
||||
} else {
|
||||
namedTypeView = this.getEnumTypeView(language, namedType);
|
||||
namedTypeView.enum = true;
|
||||
}
|
||||
|
||||
Object.entries(namedTypes).forEach(([typeName, namedType]) => {
|
||||
if (isCustomType(namedType)) {
|
||||
namedTypesView.customTypes.push(this.getCustomTypeView(language, typeName, namedType));
|
||||
} else {
|
||||
namedTypesView.enumTypes.push(this.getEnumTypeView(language, typeName, namedType));
|
||||
}
|
||||
});
|
||||
|
||||
return namedTypesView;
|
||||
return namedTypeView;
|
||||
}
|
||||
|
||||
private getModuleView(language: RenderingLanguage, module: Module): ModuleView {
|
||||
private getModuleView(language: RenderingLanguage, module: Module, associatedTypes: NamedType[]): ModuleView {
|
||||
switch (language) {
|
||||
case RenderingLanguage.Swift:
|
||||
return new SwiftModuleView(module);
|
||||
return new SwiftModuleView(
|
||||
module,
|
||||
associatedTypes.map((associatedType) => this.getNamedTypeView(language, associatedType))
|
||||
);
|
||||
default:
|
||||
throw Error('Unhandled language');
|
||||
}
|
||||
|
@ -137,10 +158,10 @@ export class CodeGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
private getEnumTypeView(language: RenderingLanguage, typeName: string, enumType: EnumType): EnumTypeView {
|
||||
private getEnumTypeView(language: RenderingLanguage, enumType: EnumType): EnumTypeView {
|
||||
switch (language) {
|
||||
case RenderingLanguage.Swift:
|
||||
return new SwiftEnumTypeView(typeName, enumType);
|
||||
return new SwiftEnumTypeView(enumType);
|
||||
default:
|
||||
throw Error('Unhandled language');
|
||||
}
|
||||
|
@ -150,14 +171,4 @@ export class CodeGenerator {
|
|||
const filePath = path.join(outputDirectory, fileName);
|
||||
fs.writeFileSync(filePath, content);
|
||||
}
|
||||
|
||||
private pushNamedTypes(namedTypes: Record<string, NamedType>): void {
|
||||
Object.entries(namedTypes).forEach(([typeName, namedType]) => {
|
||||
if (this.namedTypes[typeName] !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.namedTypes[typeName] = namedType;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { capitalize } from '../utils';
|
||||
import {
|
||||
CustomType,
|
||||
EnumType,
|
||||
isArraryType,
|
||||
isCustomType,
|
||||
isDictionaryType,
|
||||
|
@ -9,53 +7,74 @@ import {
|
|||
isOptionalType,
|
||||
Module,
|
||||
ValueType,
|
||||
CustomType,
|
||||
EnumType,
|
||||
} from '../types';
|
||||
|
||||
export type NamedType = CustomType | EnumType;
|
||||
export type NamedType = (CustomType & { name: string }) | EnumType;
|
||||
export type NamedTypesResult = { associatedTypes: Record<string, NamedType[]>; sharedTypes: NamedType[] };
|
||||
|
||||
export function dropIPrefixInCustomTypes(modules: Module[]): void {
|
||||
fetchRootTypes(modules).forEach((valueType) => {
|
||||
recursiveVisitNamedType(valueType, (namedType) => {
|
||||
if (!isCustomType(namedType)) {
|
||||
return;
|
||||
}
|
||||
modules
|
||||
.flatMap((module) => fetchRootTypes(module))
|
||||
.forEach((valueType) => {
|
||||
recursiveVisitNamedType(valueType, (namedType) => {
|
||||
if (!isCustomType(namedType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
namedType.name = namedType.name?.replace(/^I/, '');
|
||||
namedType.name = namedType.name?.replace(/^I/, '');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchNamedTypes(modules: Module[]): Record<string, NamedType> {
|
||||
const typeMap: Record<string, NamedType> = {};
|
||||
export function fetchNamedTypes(modules: Module[]): NamedTypesResult {
|
||||
const typeMap: Record<string, { namedType: NamedType; associatedModules: Set<string> }> = {};
|
||||
|
||||
fetchRootTypes(modules).forEach((valueType) => {
|
||||
recursiveVisitNamedType(valueType, (namedType, path) => {
|
||||
if (namedType.name === undefined) {
|
||||
namedType.name = path;
|
||||
}
|
||||
modules.forEach((module) => {
|
||||
fetchRootTypes(module).forEach((valueType) => {
|
||||
recursiveVisitNamedType(valueType, (namedType, path) => {
|
||||
if (namedType.name === undefined) {
|
||||
namedType.name = path;
|
||||
}
|
||||
|
||||
if (typeMap[namedType.name] !== undefined) {
|
||||
return;
|
||||
}
|
||||
if (typeMap[namedType.name] === undefined) {
|
||||
typeMap[namedType.name] = { namedType: namedType as NamedType, associatedModules: new Set() };
|
||||
}
|
||||
|
||||
typeMap[namedType.name] = namedType;
|
||||
typeMap[namedType.name].associatedModules.add(module.name);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return typeMap;
|
||||
const associatedTypes: Record<string, NamedType[]> = {};
|
||||
const sharedTypes: NamedType[] = [];
|
||||
|
||||
Object.values(typeMap).forEach(({ namedType, 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);
|
||||
} else {
|
||||
sharedTypes.push(namedType);
|
||||
}
|
||||
});
|
||||
|
||||
return { associatedTypes, sharedTypes };
|
||||
}
|
||||
|
||||
function fetchRootTypes(modules: Module[]): ValueType[] {
|
||||
return modules
|
||||
.flatMap((module) => module.methods)
|
||||
.flatMap((method) =>
|
||||
method.parameters.map((parameter) => parameter.type).concat(method.returnType ? [method.returnType] : [])
|
||||
);
|
||||
function fetchRootTypes(module: Module): ValueType[] {
|
||||
return module.methods.flatMap((method) =>
|
||||
method.parameters.map((parameter) => parameter.type).concat(method.returnType ? [method.returnType] : [])
|
||||
);
|
||||
}
|
||||
|
||||
function recursiveVisitNamedType(
|
||||
valueType: ValueType,
|
||||
visit: (namedType: NamedType, path: string) => void,
|
||||
visit: (namedType: CustomType | EnumType, path: string) => void,
|
||||
path = ''
|
||||
): void {
|
||||
if (isCustomType(valueType)) {
|
||||
|
|
57
src/index.ts
57
src/index.ts
|
@ -2,27 +2,33 @@ import yargs from 'yargs';
|
|||
import { CodeGenerator, RenderingLanguage } from './generator/CodeGenerator';
|
||||
import { parseKeyValueText } from './utils';
|
||||
|
||||
interface Config {
|
||||
moduleGenerationMaps: { interfacePaths: string[]; moduleTemplatePath: string; outputDirectory: string }[];
|
||||
namedTypesTemplatePath: string;
|
||||
namedTypesOutputPath: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
defaultCustomTags?: any;
|
||||
dropInterfaceIPrefix?: boolean;
|
||||
}
|
||||
const program = yargs(process.argv.slice(2));
|
||||
|
||||
const args = program
|
||||
.config()
|
||||
.options({
|
||||
interfacePaths: {
|
||||
type: 'string',
|
||||
array: true,
|
||||
demandOption: true,
|
||||
describe: 'The path of api interface which should extend IExportedApi',
|
||||
},
|
||||
outputDirectory: {
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
describe: 'The path of output directory',
|
||||
},
|
||||
moduleTemplatePath: {
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
describe: 'The path of module template',
|
||||
},
|
||||
namedTypesTemplate: {
|
||||
namedTypesTemplatePath: {
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
describe: 'The path of named types template',
|
||||
|
@ -30,7 +36,7 @@ const args = program
|
|||
defaultCustomTag: {
|
||||
type: 'string',
|
||||
array: true,
|
||||
coerce: (values: string[]) => values.map(tagString => parseKeyValueText(tagString)),
|
||||
coerce: (values: string[]) => values.map((tagString) => parseKeyValueText(tagString)),
|
||||
default: [],
|
||||
describe: 'Default values for custom tags',
|
||||
},
|
||||
|
@ -43,20 +49,39 @@ const args = program
|
|||
.help().argv;
|
||||
|
||||
function run(): void {
|
||||
const config = args as unknown as Config;
|
||||
|
||||
const generator = new CodeGenerator();
|
||||
generator.parse({
|
||||
tag: 'APIs',
|
||||
interfacePaths: args.interfacePaths,
|
||||
defaultCustomTags: Object.fromEntries(args.defaultCustomTag.map(tag => [tag.key, tag.value])),
|
||||
dropInterfaceIPrefix: args.dropInterfaceIPrefix,
|
||||
config.moduleGenerationMaps.forEach((moduleGenerationMap, index) => {
|
||||
generator.parse({
|
||||
interfacePaths: moduleGenerationMap.interfacePaths,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
defaultCustomTags: config.defaultCustomTags ?? {},
|
||||
dropInterfaceIPrefix: config.dropInterfaceIPrefix ?? false,
|
||||
});
|
||||
generator.printModules(index);
|
||||
});
|
||||
generator.printModules({ tag: 'APIs' });
|
||||
generator.render({
|
||||
tag: 'APIs',
|
||||
|
||||
generator.parseNamedTypes();
|
||||
generator.printSharedNamedTypes();
|
||||
|
||||
config.moduleGenerationMaps.forEach((_, index) => {
|
||||
generator.printModules(index);
|
||||
});
|
||||
|
||||
config.moduleGenerationMaps.forEach((moduleGenerationMap, index) => {
|
||||
generator.renderModules({
|
||||
index,
|
||||
language: RenderingLanguage.Swift,
|
||||
outputDirectory: moduleGenerationMap.outputDirectory,
|
||||
moduleTemplatePath: moduleGenerationMap.moduleTemplatePath,
|
||||
});
|
||||
});
|
||||
|
||||
generator.renderNamedTypes({
|
||||
language: RenderingLanguage.Swift,
|
||||
outputDirectory: args.outputDirectory,
|
||||
moduleTemplatePath: args.moduleTemplatePath,
|
||||
namedTypesTemplatePath: args.namedTypesTemplate,
|
||||
namedTypesTemplatePath: config.namedTypesTemplatePath,
|
||||
namedTypesOutputPath: config.namedTypesOutputPath,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,11 @@ export class Parser {
|
|||
parse(): Module[] {
|
||||
const modules: Module[] = [];
|
||||
|
||||
this.program.getSourceFiles().forEach((sourceFile) => {
|
||||
this.program.getRootFileNames().forEach((fileName) => {
|
||||
const sourceFile = this.program.getSourceFile(fileName);
|
||||
if (sourceFile === undefined) {
|
||||
throw Error('Source file not found');
|
||||
}
|
||||
ts.forEachChild(sourceFile, (node) => {
|
||||
const module = this.moduleFromNode(node);
|
||||
if (module !== null) {
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import Mustache from 'mustache';
|
||||
|
||||
export function renderCode<View>(templatePath: string, view: View): string {
|
||||
const template = fs.readFileSync(templatePath).toString();
|
||||
return Mustache.render(template, view);
|
||||
const directory = path.dirname(templatePath);
|
||||
return Mustache.render(template, view, (partialName) => {
|
||||
const partialPath = path.join(directory, `${partialName}.mustache`);
|
||||
return fs.readFileSync(partialPath).toString();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
import { uncapitalize } from '../../utils';
|
||||
import { EnumSubType, EnumType } from '../../types';
|
||||
import { EnumTypeView } from '../views';
|
||||
|
||||
export class SwiftEnumTypeView implements EnumTypeView {
|
||||
constructor(readonly typeName: string, private enumType: EnumType) {}
|
||||
constructor(private enumType: EnumType) {}
|
||||
|
||||
get typeName(): string {
|
||||
return this.enumType.name;
|
||||
}
|
||||
|
||||
get valueType(): string {
|
||||
switch (this.enumType.subType) {
|
||||
|
@ -17,7 +22,8 @@ export class SwiftEnumTypeView implements EnumTypeView {
|
|||
|
||||
get members(): { key: string; value: string }[] {
|
||||
return Object.entries(this.enumType.members).map(([key, value]) => ({
|
||||
key,
|
||||
// TODO: Convert to camel case instead of uncapitalize
|
||||
key: uncapitalize(key),
|
||||
value: typeof value === 'string' ? `"${value}"` : `${value}`,
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Module } from '../../types';
|
||||
import { ModuleView, MethodView } from '../views';
|
||||
import { ModuleView, MethodView, NamedTypeView } from '../views';
|
||||
import { SwiftMethodView } from './SwiftMethodView';
|
||||
|
||||
export class SwiftModuleView implements ModuleView {
|
||||
constructor(private module: Module) {}
|
||||
constructor(private readonly module: Module, readonly associatedTypes: NamedTypeView[]) {}
|
||||
|
||||
get moduleName(): string {
|
||||
return this.module.name;
|
||||
|
|
|
@ -9,13 +9,11 @@ export interface MethodView {
|
|||
export interface ModuleView {
|
||||
readonly moduleName: string;
|
||||
readonly methods: MethodView[];
|
||||
readonly associatedTypes: NamedTypeView[];
|
||||
readonly customTags: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface NamedTypesView {
|
||||
readonly customTypes: CustomTypeView[];
|
||||
readonly enumTypes: EnumTypeView[];
|
||||
}
|
||||
export type NamedTypeView = (CustomTypeView | EnumTypeView) & { custom?: boolean; enum?: boolean };
|
||||
|
||||
export interface CustomTypeView {
|
||||
readonly typeName: string;
|
||||
|
|
|
@ -18,7 +18,8 @@ const typeColor = chalk.yellow;
|
|||
const valueColor = chalk.cyan;
|
||||
const documentationColor = chalk.gray;
|
||||
|
||||
export function serializeModule(module: Module): string {
|
||||
export function serializeModule(module: Module, associatedTypes: NamedType[]): string {
|
||||
const serializedAssociatedTypes = associatedTypes.map((associatedType) => serializeNamedType(associatedType));
|
||||
const customTags =
|
||||
Object.keys(module.customTags).length > 0 ? `Custom tags: ${JSON.stringify(module.customTags)}\n` : '';
|
||||
|
||||
|
@ -32,13 +33,21 @@ ${module.methods
|
|||
.map((line) => ` ${line}`)
|
||||
.join('\n')
|
||||
)
|
||||
.join('\n')}
|
||||
.join('\n')}${
|
||||
serializedAssociatedTypes.length > 0
|
||||
? `\n\n${serializedAssociatedTypes
|
||||
.join('\n')
|
||||
.split('\n')
|
||||
.map((line) => ` ${line}`)
|
||||
.join('\n')}`
|
||||
: ''
|
||||
}
|
||||
}`;
|
||||
}
|
||||
|
||||
export function serializeNamedType(typeName: string, namedType: NamedType): string {
|
||||
export function serializeNamedType(namedType: NamedType): string {
|
||||
if (isCustomType(namedType)) {
|
||||
return `${keywordColor('Type')} ${typeName} {
|
||||
return `${keywordColor('Type')} ${namedType.name} {
|
||||
${namedType.members
|
||||
.map(
|
||||
(member) =>
|
||||
|
@ -48,7 +57,7 @@ ${namedType.members
|
|||
}`;
|
||||
}
|
||||
|
||||
return `${keywordColor('Enum')} ${typeName} {
|
||||
return `${keywordColor('Enum')} ${namedType.name} {
|
||||
${Object.entries(namedType.members)
|
||||
.map(([key, value]) => ` ${identifierColor(key)} = ${valueColor(value)}`)
|
||||
.join('\n')}
|
||||
|
|
12
src/utils.ts
12
src/utils.ts
|
@ -6,8 +6,16 @@ export function capitalize(text: string): string {
|
|||
return text[0].toUpperCase() + text.slice(1);
|
||||
}
|
||||
|
||||
export function uncapitalize(text: string): string {
|
||||
if (text.length === 0) {
|
||||
return text;
|
||||
}
|
||||
|
||||
return text[0].toLowerCase() + text.slice(1);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function parseKeyValueText(text: string): { key: string, value: any } {
|
||||
export function parseKeyValueText(text: string): [string, any] {
|
||||
const index = text.indexOf('=');
|
||||
if (index === -1) {
|
||||
throw Error('Invalid custom tag');
|
||||
|
@ -24,5 +32,5 @@ export function parseKeyValueText(text: string): { key: string, value: any } {
|
|||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
return { key, value };
|
||||
return [key, value];
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ describe('Parser', () => {
|
|||
`;
|
||||
withTempParser(exportedTrueSourceCode, parser => {
|
||||
const modules = parser.parse();
|
||||
expect(modules).to.deep.equal([{name: 'ExportTrueInterface', methods: [], documentation: ''}]);
|
||||
expect(modules).to.deep.equal([{name: 'ExportTrueInterface', methods: [], documentation: '', customTags: {}}]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -52,7 +52,8 @@ describe('Parser', () => {
|
|||
returnType: null,
|
||||
documentation: 'This is an example documentation for the method',
|
||||
}],
|
||||
documentation: 'This is an example documentation for the module'
|
||||
documentation: 'This is an example documentation for the module',
|
||||
customTags: {},
|
||||
}]);
|
||||
});
|
||||
});
|
||||
|
@ -71,7 +72,7 @@ describe('Parser', () => {
|
|||
const stubWarn = sinon.stub(console, 'warn');
|
||||
|
||||
const modules = parser.parse();
|
||||
expect(modules).to.deep.equal([{name: 'MockedInterface', methods: [], documentation: ''}]);
|
||||
expect(modules).to.deep.equal([{name: 'MockedInterface', methods: [], documentation: '', customTags: {}}]);
|
||||
|
||||
const expectedWarning = warnMessage(`Skipped "invalidProperty: string;" at ${filePath}:5 because it is not valid method signature. Please define only methods.`);
|
||||
expect(stubWarn).to.have.been.calledWith(expectedWarning);
|
||||
|
@ -94,7 +95,7 @@ describe('Parser', () => {
|
|||
const stubWarn = sinon.stub(console, 'warn');
|
||||
|
||||
const modules = parser.parse();
|
||||
expect(modules).to.deep.equal([{name: 'MockedInterface', methods: [], documentation: ''}]);
|
||||
expect(modules).to.deep.equal([{name: 'MockedInterface', methods: [], documentation: '', customTags: {}}]);
|
||||
|
||||
const expectedWarning = warnMessage(`Skipped "multipleParamsMethod(foo: string, bar: number);" at ${filePath}:5 because it has multiple parameters. Methods should only have one property. Please use destructuring object for multiple parameters.`);
|
||||
expect(stubWarn).to.have.been.calledWith(expectedWarning);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"include": ["src/*.ts", "src/**/*.ts", "test/*.ts", "test/**/*.ts"],
|
||||
"include": ["src/*.ts", "src/**/*.ts"],
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"target": "es6",
|
||||
|
|
3727
yarn.lock
3727
yarn.lock
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Загрузка…
Ссылка в новой задаче