зеркало из https://github.com/microsoft/ts-gyb.git
Initialize project
This commit is contained in:
Родитель
7a89fe576f
Коммит
ad0b71cd04
|
@ -0,0 +1,78 @@
|
|||
// eslint-disable-next-line no-undef
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
"project": "tsconfig.json",
|
||||
"sourceType": "module"
|
||||
},
|
||||
env: {
|
||||
"browser": true,
|
||||
},
|
||||
plugins: [
|
||||
"@typescript-eslint",
|
||||
],
|
||||
extends: [
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
||||
"eslint-config-airbnb-base",
|
||||
"eslint-config-prettier"
|
||||
],
|
||||
settings: {
|
||||
"import/resolver": {
|
||||
"node": {
|
||||
"extensions": [".ts"]
|
||||
}
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
"import/no-unresolved": [2],
|
||||
"@typescript-eslint/explicit-function-return-type": "error",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "error",
|
||||
"@typescript-eslint/prefer-regexp-exec": "warn",
|
||||
"@typescript-eslint/restrict-template-expressions": "warn",
|
||||
"@typescript-eslint/no-unsafe-member-access": "warn",
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": "warn",
|
||||
"@typescript-eslint/no-unsafe-return": "warn",
|
||||
"@typescript-eslint/no-unsafe-call": "warn",
|
||||
"@typescript-eslint/no-unsafe-assignment": "warn",
|
||||
"@typescript-eslint/no-empty-interface": "off",
|
||||
"@typescript-eslint/no-namespace": "off",
|
||||
"@typescript-eslint/semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
"no-shadow": "off",
|
||||
"@typescript-eslint/no-shadow": ["error"],
|
||||
"no-use-before-define": "off",
|
||||
"@typescript-eslint/no-use-before-define": [
|
||||
"error",
|
||||
{ "functions": false }
|
||||
],
|
||||
"no-unused-vars": "off",
|
||||
"no-useless-escape": "warn",
|
||||
"no-prototype-builtins": "warn",
|
||||
"no-console": "off",
|
||||
"arrow-parens": [
|
||||
"off",
|
||||
"always"
|
||||
],
|
||||
"sort-keys": "off",
|
||||
"max-len": "off",
|
||||
"no-bitwise": "off",
|
||||
"no-duplicate-case": "error",
|
||||
"quotes": ["off", "single"],
|
||||
"curly": "error",
|
||||
"import/extensions": "off",
|
||||
"class-methods-use-this": "off",
|
||||
"no-plusplus": ["error", { "allowForLoopAfterthoughts": true }],
|
||||
"camelcase": ["error", {"allow": ["UNSAFE_componentWillReceiveProps"]}],
|
||||
"import/prefer-default-export": "off",
|
||||
"import/no-default-export": "error",
|
||||
"no-param-reassign": ["error", { "props": false }],
|
||||
"no-underscore-dangle": ["error", { "enforceInMethodNames": true, "allowAfterThis": true }],
|
||||
"no-useless-constructor": "off",
|
||||
"no-empty-function": ["error", {"allow": ["constructors"]}],
|
||||
}
|
||||
};
|
|
@ -0,0 +1,116 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
|
@ -0,0 +1,43 @@
|
|||
name: $(Build.BuildId)
|
||||
trigger:
|
||||
batch: true
|
||||
branches:
|
||||
include:
|
||||
- '*'
|
||||
tags:
|
||||
include:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
- job: BuildJavaScript
|
||||
displayName: Build JavaScript
|
||||
pool:
|
||||
vmName: ubuntu-18.04
|
||||
demands:
|
||||
- npm
|
||||
|
||||
steps:
|
||||
- task: Npm@1
|
||||
displayName: 'npm cache clean --force'
|
||||
inputs:
|
||||
command: custom
|
||||
verbose: false
|
||||
customCommand: 'cache clean --force'
|
||||
|
||||
- task: Npm@1
|
||||
displayName: 'npm install'
|
||||
|
||||
- task: Npm@1
|
||||
displayName: 'npm run lint:ci'
|
||||
inputs:
|
||||
command: custom
|
||||
verbose: false
|
||||
customCommand: 'run lint:ci'
|
||||
|
||||
- task: Npm@1
|
||||
displayName: 'npm run build'
|
||||
inputs:
|
||||
command: custom
|
||||
verbose: false
|
||||
customCommand: 'run build'
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
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)
|
||||
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"name": "ts-codegen-swift",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "gulp build",
|
||||
"clean": "gulp clean",
|
||||
"start": "gulp start",
|
||||
"dev": "gulp dev",
|
||||
"start:example": "ts-node ./src/example/exampleDemo",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"lint": "npx eslint ./src --ext .js,.jsx,.ts,.tsx",
|
||||
"prettier:write": "npx prettier --write \"src/**/*.ts\"",
|
||||
"prettier:check": "npx prettier --check \"src/**/*.ts\"",
|
||||
"lint:fix": "npm run lint -- --fix && npm run prettier:write",
|
||||
"lint:ci": "npm run lint && npm run prettier:check"
|
||||
},
|
||||
"bin": {
|
||||
"ts-codegen-swift": "dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.js"
|
||||
],
|
||||
"types": "typings/*/**.d.ts",
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "private",
|
||||
"devDependencies": {
|
||||
"@types/gulp": "^4.0.6",
|
||||
"@types/yargs": "^15.0.9",
|
||||
"@typescript-eslint/eslint-plugin": "^4.9.0",
|
||||
"@typescript-eslint/parser": "^4.9.0",
|
||||
"del": "^5.1.0",
|
||||
"eslint": "^7.5.0",
|
||||
"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",
|
||||
"prettier": "^2.2.1",
|
||||
"ts-node": "^8.10.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"glob": "^7.1.6",
|
||||
"typescript": "^3.9.7",
|
||||
"yargs": "^16.1.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
module.exports = {
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"singleQuote": true,
|
||||
"semi": true,
|
||||
"trailingComma": "es5",
|
||||
"bracketSpacing": true,
|
||||
"parser": "babel-ts",
|
||||
"quoteProps": "as-needed",
|
||||
"jsxBracketSameLine": false,
|
||||
}
|
|
@ -0,0 +1,394 @@
|
|||
import ts from 'typescript';
|
||||
import { glob } from 'glob';
|
||||
import {
|
||||
Module,
|
||||
Method,
|
||||
Field,
|
||||
ValueType,
|
||||
BasicTypeValue,
|
||||
CustomTypeKind,
|
||||
ArrayTypeKind,
|
||||
BasicTypeKind,
|
||||
ValueTypeKindFlag,
|
||||
} from './types';
|
||||
|
||||
export class Parser {
|
||||
program: ts.Program;
|
||||
|
||||
checker: ts.TypeChecker;
|
||||
|
||||
constructor(globPatterns: string[]) {
|
||||
const filePaths = globPatterns.flatMap((pattern) => glob.sync(pattern));
|
||||
this.program = ts.createProgram({
|
||||
rootNames: filePaths,
|
||||
options: {},
|
||||
});
|
||||
this.checker = this.program.getTypeChecker();
|
||||
}
|
||||
|
||||
parse = (): Module[] => {
|
||||
const modules: Module[] = [];
|
||||
this.program.getSourceFiles().forEach((sourceFile) => {
|
||||
ts.forEachChild(sourceFile, (node) => {
|
||||
const module = this.moduleFromNode(node);
|
||||
if (module !== null) {
|
||||
modules.push(module);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return modules;
|
||||
};
|
||||
|
||||
private moduleFromNode = (node: ts.Node): Module | null => {
|
||||
if (!this.isNodeExported(node) || !ts.isInterfaceDeclaration(node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (node.name === undefined) {
|
||||
return null;
|
||||
}
|
||||
const interfaceName = node.name?.text;
|
||||
|
||||
if (node.members === undefined || node.members === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.isNodeExtended(node, 'IExportedApi')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const methods: Method[] = [];
|
||||
node.members.forEach((methodNode) => {
|
||||
if (!ts.isPropertySignature(methodNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const methodType = methodNode.type;
|
||||
if (!methodType) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ts.isFunctionTypeNode(methodType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const method = this.methodFromMethodNode(methodNode.name.getText(), methodType);
|
||||
if (method) {
|
||||
methods.push(method);
|
||||
}
|
||||
});
|
||||
|
||||
if (methods.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
name: interfaceName,
|
||||
methods,
|
||||
};
|
||||
};
|
||||
|
||||
private methodFromMethodNode = (methodName: string, node: ts.FunctionTypeNode): Method | null => {
|
||||
let parameters: Field[] = [];
|
||||
const fields = this.fieldsFromFunctionTypeNodeForParameters(node, methodName);
|
||||
if (fields !== null) {
|
||||
parameters = fields;
|
||||
}
|
||||
|
||||
let returnValueType: ValueType | null = null;
|
||||
|
||||
if (node.type !== undefined) {
|
||||
const valueType = this.valueTypeFromNode(node, `${methodName}Return`);
|
||||
if (valueType !== null) {
|
||||
returnValueType = valueType;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: methodName,
|
||||
parameters,
|
||||
returnType: returnValueType,
|
||||
};
|
||||
};
|
||||
|
||||
private valueTypeFromNode = (
|
||||
node: ts.Node & { type?: ts.TypeNode; questionToken?: ts.QuestionToken },
|
||||
literalTypeDescription: string
|
||||
): ValueType | null => {
|
||||
if (node.type === undefined) {
|
||||
return null;
|
||||
}
|
||||
const nullable = this.isValueTypeNullableFromNode(node);
|
||||
|
||||
return this.valueTypeFromTypeNode(node.type, nullable, literalTypeDescription);
|
||||
};
|
||||
|
||||
private isValueTypeNullableFromNode = (node: ts.Node & { type?: ts.TypeNode; questionToken?: ts.QuestionToken }): boolean => {
|
||||
const nullable = node.questionToken !== undefined;
|
||||
|
||||
return nullable;
|
||||
};
|
||||
|
||||
private valueTypeFromTypeNode = (
|
||||
typeNode: ts.TypeNode,
|
||||
nullable: boolean,
|
||||
literalTypeDescription: string
|
||||
): ValueType | null => {
|
||||
if (ts.isUnionTypeNode(typeNode)) {
|
||||
return this.extractUnionTypeNode(typeNode, literalTypeDescription, nullable);
|
||||
}
|
||||
|
||||
const typeKind = this.basicTypeKindFromTypeNode(typeNode);
|
||||
if (typeKind !== null) {
|
||||
return {
|
||||
kind: typeKind,
|
||||
nullable,
|
||||
};
|
||||
}
|
||||
|
||||
let customTypeKind = this.referenceTypeKindFromTypeNode(typeNode);
|
||||
if (customTypeKind !== null) {
|
||||
return {
|
||||
kind: customTypeKind,
|
||||
nullable,
|
||||
};
|
||||
}
|
||||
|
||||
customTypeKind = this.literalTypeKindFromTypeNode(typeNode, literalTypeDescription);
|
||||
if (customTypeKind !== null) {
|
||||
return {
|
||||
kind: customTypeKind,
|
||||
nullable,
|
||||
};
|
||||
}
|
||||
|
||||
const arrayTypeKind = this.arrayTypeKindFromTypeNode(typeNode, `${literalTypeDescription}Array`);
|
||||
if (arrayTypeKind !== null) {
|
||||
return {
|
||||
kind: arrayTypeKind,
|
||||
nullable,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
private fieldsFromFunctionTypeNodeForParameters = (
|
||||
node: ts.FunctionTypeNode,
|
||||
literalTypeDescription: string
|
||||
): Field[] | null => {
|
||||
if (!node.parameters || !node.parameters.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return node.parameters
|
||||
.map((item) =>
|
||||
this.fieldFromParameter(
|
||||
item,
|
||||
`${literalTypeDescription}Parameters${this.getNameWithCapitalFirstLetter(item.name.getText())}`
|
||||
)
|
||||
)
|
||||
.filter((field): field is Field => field !== null);
|
||||
};
|
||||
|
||||
private fieldFromParameter = (node: ts.ParameterDeclaration, literalTypeDescription: string): Field | null => {
|
||||
const name = node.name.getText();
|
||||
|
||||
const valueType = this.valueTypeFromNode(node, literalTypeDescription);
|
||||
if (valueType !== null) {
|
||||
return {
|
||||
name,
|
||||
type: valueType,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
private fieldsFromLiteralTypeNode = (type: ts.TypeNode, literalTypeDescription: string): Field[] | null => {
|
||||
if (!ts.isTypeLiteralNode(type)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return type.members
|
||||
.map((item, index) =>
|
||||
this.fieldFromTypeElement(
|
||||
item,
|
||||
`${literalTypeDescription}Members${this.getNameWithCapitalFirstLetter(item.name?.getText()) || index}`
|
||||
)
|
||||
)
|
||||
.filter((field): field is Field => field !== null);
|
||||
};
|
||||
|
||||
private fieldFromTypeElement = (node: ts.TypeElement, literalTypeDescription: string): Field | null => {
|
||||
if (!ts.isPropertySignature(node) || node.type === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let name = node.name.getText();
|
||||
if (!name || name === '__type') {
|
||||
name = `${literalTypeDescription}Type`;
|
||||
}
|
||||
|
||||
const valueType = this.valueTypeFromNode(node, literalTypeDescription);
|
||||
if (valueType !== null) {
|
||||
return {
|
||||
name,
|
||||
type: valueType,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
private basicTypeKindFromTypeNode = (node: ts.TypeNode): BasicTypeKind | null => {
|
||||
if (node.kind === ts.SyntaxKind.StringKeyword) {
|
||||
return {
|
||||
flag: ValueTypeKindFlag.basicType,
|
||||
value: BasicTypeValue.string,
|
||||
};
|
||||
}
|
||||
if (node.kind === ts.SyntaxKind.NumberKeyword) {
|
||||
return {
|
||||
flag: ValueTypeKindFlag.basicType,
|
||||
value: BasicTypeValue.number,
|
||||
};
|
||||
}
|
||||
if (node.kind === ts.SyntaxKind.BooleanKeyword) {
|
||||
return {
|
||||
flag: ValueTypeKindFlag.basicType,
|
||||
value: BasicTypeValue.boolean,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
private referenceTypeKindFromTypeNode = (node: ts.TypeNode): CustomTypeKind | null => {
|
||||
if (!ts.isTypeReferenceNode(node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const declarations = this.checker.getTypeFromTypeNode(node).symbol.getDeclarations();
|
||||
if (declarations === undefined || declarations.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const declNode = declarations[0];
|
||||
if (!ts.isInterfaceDeclaration(declNode)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const name = declNode.name.getText();
|
||||
|
||||
const members = declNode.members
|
||||
.map((item, index) =>
|
||||
this.fieldFromTypeElement(item, `${name}Members${this.getNameWithCapitalFirstLetter(item.name?.getText()) || index}`)
|
||||
)
|
||||
.filter((field): field is Field => field !== null);
|
||||
|
||||
return {
|
||||
flag: ValueTypeKindFlag.customType,
|
||||
name,
|
||||
members,
|
||||
};
|
||||
};
|
||||
|
||||
private literalTypeKindFromTypeNode = (node: ts.TypeNode, literalTypeDescription: string): CustomTypeKind | null => {
|
||||
if (!ts.isTypeLiteralNode(node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fields = this.fieldsFromLiteralTypeNode(node, literalTypeDescription);
|
||||
if (fields) {
|
||||
return {
|
||||
flag: ValueTypeKindFlag.customType,
|
||||
name: `${literalTypeDescription}Type`,
|
||||
isTypeLiteral: true,
|
||||
members: fields,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
private arrayTypeKindFromTypeNode = (node: ts.TypeNode, literalTypeDescription: string): ArrayTypeKind | null => {
|
||||
if (!ts.isArrayTypeNode(node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const elementType = this.valueTypeFromTypeNode(node.elementType, false, `${literalTypeDescription}Element`);
|
||||
if (elementType) {
|
||||
return {
|
||||
flag: ValueTypeKindFlag.arrayType,
|
||||
elementType,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
private extractUnionTypeNode = (node: ts.Node, literalTypeDescription: string, nullable: boolean): ValueType | null => {
|
||||
if (!ts.isUnionTypeNode(node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let isNullable = nullable;
|
||||
let kind: ValueType['kind'] | undefined;
|
||||
|
||||
node.types.forEach((typeNode) => {
|
||||
if (!isNullable && this.isUndefinedOrNull(typeNode)) {
|
||||
isNullable = true;
|
||||
}
|
||||
if (!kind && !this.isUndefinedOrNull(typeNode)) {
|
||||
kind = this.valueTypeFromTypeNode(typeNode, false, literalTypeDescription)?.kind;
|
||||
}
|
||||
});
|
||||
|
||||
if (!kind) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
kind,
|
||||
nullable: isNullable,
|
||||
};
|
||||
};
|
||||
|
||||
private isUndefinedOrNull = (node: ts.TypeNode): boolean => {
|
||||
if (node.kind === ts.SyntaxKind.UndefinedKeyword) {
|
||||
return true;
|
||||
}
|
||||
if (node.kind === ts.SyntaxKind.NullKeyword) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
private isNodeExported = (node: ts.Node): boolean =>
|
||||
(ts.getCombinedModifierFlags(node as ts.Declaration) & ts.ModifierFlags.Export) !== 0 ||
|
||||
(!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile);
|
||||
|
||||
private isNodeExtended(node: ts.InterfaceDeclaration, extendedInterfaceName: string): boolean {
|
||||
if (!node.heritageClauses?.length) {
|
||||
return false;
|
||||
}
|
||||
const extendHeritageClause = node.heritageClauses.find((item) => item.token === ts.SyntaxKind.ExtendsKeyword);
|
||||
if (!extendHeritageClause) {
|
||||
return false;
|
||||
}
|
||||
const extendedInterface = extendHeritageClause.types.find(
|
||||
(item) => this.checker.getTypeAtLocation(item).symbol.escapedName === extendedInterfaceName
|
||||
);
|
||||
return !!extendedInterface;
|
||||
}
|
||||
|
||||
private getNameWithCapitalFirstLetter(name?: string): string | undefined {
|
||||
let targetName = name;
|
||||
if (name && name.length > 0) {
|
||||
targetName = name[0].toUpperCase() + name.slice(1);
|
||||
}
|
||||
return targetName;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import { IExportedApi } from '../../index';
|
||||
|
||||
export interface IHtmlApi extends IExportedApi {
|
||||
getHeight: () => number;
|
||||
getHeightWithBottomAnchor: (sta: string[]) => number;
|
||||
getHTML: () => string;
|
||||
requestRenderingResult: () => void;
|
||||
}
|
||||
|
||||
export interface IImageOptionApi extends IExportedApi {
|
||||
hideElementWithID: (ID: string) => void;
|
||||
restoreElementVisibilityWithID: (ID: string) => void;
|
||||
getSourceOfImageWithID: (ID: string) => string | null;
|
||||
getImageDataList: () => string;
|
||||
getContentBoundsOfElementWithID: (ID: string) => string | null;
|
||||
}
|
|
@ -0,0 +1,253 @@
|
|||
[
|
||||
{
|
||||
"name": "IHtmlApi",
|
||||
"methods": [
|
||||
{
|
||||
"name": "getHeight",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "test",
|
||||
"type": {
|
||||
"kind": {
|
||||
"flag": "customType",
|
||||
"name": "Test",
|
||||
"members": [
|
||||
{
|
||||
"name": "st",
|
||||
"type": {
|
||||
"kind": {
|
||||
"flag": "basicType",
|
||||
"value": "string"
|
||||
},
|
||||
"nullable": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "test2",
|
||||
"type": {
|
||||
"kind": {
|
||||
"flag": "customType",
|
||||
"name": "getHeightParametersTest2Type",
|
||||
"isTypeLiteral": true,
|
||||
"members": [
|
||||
{
|
||||
"name": "st",
|
||||
"type": {
|
||||
"kind": {
|
||||
"flag": "basicType",
|
||||
"value": "string"
|
||||
},
|
||||
"nullable": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "embed",
|
||||
"type": {
|
||||
"kind": {
|
||||
"flag": "customType",
|
||||
"name": "getHeightParametersTest2MembersEmbedType",
|
||||
"isTypeLiteral": true,
|
||||
"members": [
|
||||
{
|
||||
"name": "a",
|
||||
"type": {
|
||||
"kind": {
|
||||
"flag": "basicType",
|
||||
"value": "string"
|
||||
},
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"nullable": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"returnType": {
|
||||
"kind": {
|
||||
"flag": "arrayType",
|
||||
"elementType": {
|
||||
"kind": {
|
||||
"flag": "customType",
|
||||
"name": "Test",
|
||||
"members": [
|
||||
{
|
||||
"name": "st",
|
||||
"type": {
|
||||
"kind": {
|
||||
"flag": "basicType",
|
||||
"value": "string"
|
||||
},
|
||||
"nullable": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"nullable": false
|
||||
}
|
||||
},
|
||||
"nullable": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "getHeightWithBottomAnchor",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "sta",
|
||||
"type": {
|
||||
"kind": {
|
||||
"flag": "arrayType",
|
||||
"elementType": {
|
||||
"kind": {
|
||||
"flag": "basicType",
|
||||
"value": "string"
|
||||
},
|
||||
"nullable": false
|
||||
}
|
||||
},
|
||||
"nullable": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"returnType": {
|
||||
"kind": {
|
||||
"flag": "basicType",
|
||||
"value": "number"
|
||||
},
|
||||
"nullable": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "getHTML",
|
||||
"parameters": [],
|
||||
"returnType": {
|
||||
"kind": {
|
||||
"flag": "customType",
|
||||
"name": "Test",
|
||||
"members": [
|
||||
{
|
||||
"name": "st",
|
||||
"type": {
|
||||
"kind": {
|
||||
"flag": "basicType",
|
||||
"value": "string"
|
||||
},
|
||||
"nullable": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"nullable": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "requestRenderingResult",
|
||||
"parameters": [],
|
||||
"returnType": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "IImageOptionApi",
|
||||
"methods": [
|
||||
{
|
||||
"name": "hideElementWithID",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ID",
|
||||
"type": {
|
||||
"kind": {
|
||||
"flag": "basicType",
|
||||
"value": "string"
|
||||
},
|
||||
"nullable": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"returnType": null
|
||||
},
|
||||
{
|
||||
"name": "restoreElementVisibilityWithID",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ID",
|
||||
"type": {
|
||||
"kind": {
|
||||
"flag": "basicType",
|
||||
"value": "string"
|
||||
},
|
||||
"nullable": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"returnType": null
|
||||
},
|
||||
{
|
||||
"name": "getSourceOfImageWithID",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ID",
|
||||
"type": {
|
||||
"kind": {
|
||||
"flag": "basicType",
|
||||
"value": "string"
|
||||
},
|
||||
"nullable": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"returnType": {
|
||||
"kind": {
|
||||
"flag": "basicType",
|
||||
"value": "string"
|
||||
},
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "getImageDataList",
|
||||
"parameters": [],
|
||||
"returnType": {
|
||||
"kind": {
|
||||
"flag": "basicType",
|
||||
"value": "string"
|
||||
},
|
||||
"nullable": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "getContentBoundsOfElementWithID",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ID",
|
||||
"type": {
|
||||
"kind": {
|
||||
"flag": "basicType",
|
||||
"value": "string"
|
||||
},
|
||||
"nullable": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"returnType": {
|
||||
"kind": {
|
||||
"flag": "basicType",
|
||||
"value": "string"
|
||||
},
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -0,0 +1,23 @@
|
|||
import { Parser } from '../Parser';
|
||||
import { RendererConfig, DefaultSwiftRendererConfig } from '../renderer/RenderConfig';
|
||||
import { CustomTypeCollector } from '../renderer/CustomTypeCollector';
|
||||
import { ExampleCodeRenderer } from './demoCodeRenderer';
|
||||
|
||||
function run(): void {
|
||||
const config = new DefaultSwiftRendererConfig();
|
||||
console.log('Api will be generated with config: \n', config);
|
||||
|
||||
const parser = new Parser(['src/example/data/exampleApi.ts']);
|
||||
const apiModules = parser.parse();
|
||||
console.log(JSON.stringify(apiModules, null, 4));
|
||||
|
||||
const rendererConfig = config as RendererConfig;
|
||||
const typeTransformer = new CustomTypeCollector(rendererConfig);
|
||||
const renderer = new ExampleCodeRenderer(rendererConfig, typeTransformer);
|
||||
|
||||
renderer.print();
|
||||
|
||||
console.log(typeTransformer.toSourceLike().join('\n'));
|
||||
}
|
||||
|
||||
run();
|
|
@ -0,0 +1,68 @@
|
|||
import dummyData from './data/parsedModules.json';
|
||||
import { Module } from '../types';
|
||||
import { RendererConfig } from '../renderer/RenderConfig';
|
||||
import { InternalDataStructure } from '../renderer/InternalDataStructure';
|
||||
import { GenericCodeRenderer } from '../renderer/GenericCodeRenderer';
|
||||
import { TypeTransformer } from '../renderer/CustomTypeCollector';
|
||||
|
||||
export class ExampleCodeRenderer extends GenericCodeRenderer {
|
||||
constructor(rendererConfig: RendererConfig, private typeTransformer: TypeTransformer) {
|
||||
super(rendererConfig);
|
||||
}
|
||||
|
||||
render(): void {
|
||||
const dummyDataTyped = dummyData as Module[];
|
||||
dummyDataTyped.forEach((module) => {
|
||||
this.namespaces.push(module.name);
|
||||
this.emitLine(0, `// ${module.name}`);
|
||||
module.methods.forEach((method) => {
|
||||
const source = `${method.name}`;
|
||||
const parameterSources = method.parameters.map(
|
||||
(parameter) => `${parameter.name}: ${this.typeTransformer.transformType(parameter.type)}`
|
||||
);
|
||||
|
||||
let returnTypeStatement: string;
|
||||
if (method.returnType !== null) {
|
||||
const returnTypeString = this.typeTransformer.transformType(method.returnType, true);
|
||||
returnTypeStatement = `@escaping (BridgeCompletion<${returnTypeString}?>)? = nil`;
|
||||
} else {
|
||||
returnTypeStatement = `BridgeJSExecutor.Completion? = nil`;
|
||||
}
|
||||
parameterSources.push(`completion: ${returnTypeStatement})`);
|
||||
const parameterSourceStatement = parameterSources.join(', ');
|
||||
|
||||
this.emitLine(0, `func ${source}(${parameterSourceStatement}`);
|
||||
|
||||
this.emitCurlyBracketBegin();
|
||||
|
||||
const hasParam = method.parameters.length > 0;
|
||||
if (hasParam) {
|
||||
const argSource = new InternalDataStructure(
|
||||
this.rendererConfig,
|
||||
'Args',
|
||||
this.typeTransformer,
|
||||
method.parameters
|
||||
).toSourceCode();
|
||||
this.emitLines(2, argSource);
|
||||
|
||||
this.emitLine(2, `let args = Args(`);
|
||||
method.parameters.forEach((param) => this.emitLine(4, `${param.name}: ${param.name}`));
|
||||
this.emitLine(2, `)`);
|
||||
}
|
||||
const executeMethod = method.returnType !== null ? 'executeFetch' : 'execute';
|
||||
const endpoint = this.resolveEndpoint(module.name);
|
||||
this.emitLine(
|
||||
2,
|
||||
`jsExecutor.${executeMethod}(with: "${endpoint}", feature: "${method.name}", args: ${
|
||||
hasParam ? 'args' : 'nil'
|
||||
}, completion: completion)`
|
||||
);
|
||||
this.emitCurlyBracketEnd();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
print(): void {
|
||||
console.log(this.toSourceCode().join('\n'));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import yargs from 'yargs';
|
||||
import fs from 'fs';
|
||||
import { Parser } from './Parser';
|
||||
import { RendererConfig } from './renderer/RenderConfig';
|
||||
import { CustomTypeCollector } from './renderer/CustomTypeCollector';
|
||||
import { SwiftCodeRenderer } from './renderer/SwiftCodeRenderer';
|
||||
|
||||
const program = yargs(process.argv.slice(2));
|
||||
|
||||
const args = program
|
||||
.options({
|
||||
config: {
|
||||
alias: 'c',
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
describe: 'Code-generate config JSON which should implement interface RendererConfig',
|
||||
},
|
||||
path: {
|
||||
alias: 'p',
|
||||
type: 'string',
|
||||
describe: 'The path of api interface which should extend IExportedApi',
|
||||
demandOption: true,
|
||||
},
|
||||
output: {
|
||||
alias: 'o',
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
describe: 'The path of output file',
|
||||
},
|
||||
})
|
||||
.help().argv;
|
||||
|
||||
function run(): void {
|
||||
const configJson = fs.readFileSync(args.config, { encoding: 'utf8' });
|
||||
|
||||
const config = JSON.parse(configJson) as RendererConfig;
|
||||
console.log('Native Api will be generated with config: \n', config);
|
||||
|
||||
const parser = new Parser([args.path]);
|
||||
const apiModules = parser.parse();
|
||||
// console.log(JSON.stringify(result, null, 4))
|
||||
|
||||
const rendererConfig = config;
|
||||
const typeTransformer = new CustomTypeCollector(rendererConfig);
|
||||
const renderer = new SwiftCodeRenderer(rendererConfig, typeTransformer, apiModules, args.output);
|
||||
|
||||
renderer.print();
|
||||
// console.log(typeTransformer.toSourceLike().join('\n'));
|
||||
}
|
||||
|
||||
run();
|
||||
|
||||
export interface IExportedApi {}
|
||||
export type { RendererConfig } from './renderer/RenderConfig';
|
|
@ -0,0 +1,83 @@
|
|||
import { CustomTypeKind, ValueType, Field, ArrayTypeKind, BasicTypeKind, ValueTypeKindFlag } from '../types';
|
||||
import { SourceLike } from './SourceLike';
|
||||
import { RendererConfig } from './RenderConfig';
|
||||
import { InternalDataStructure } from './InternalDataStructure';
|
||||
|
||||
export interface TypeTransformer {
|
||||
transformType(fieldType: ValueType | Field[], ignoreNullable?: boolean): string;
|
||||
toSourceLike(): SourceLike[];
|
||||
}
|
||||
export class CustomTypeCollector implements TypeTransformer {
|
||||
private customTypes: Record<string, CustomTypeKind> = {};
|
||||
|
||||
constructor(protected rendererConfig: RendererConfig) {}
|
||||
|
||||
public emit(customType: CustomTypeKind): void {
|
||||
this.customTypes[customType.name] = customType;
|
||||
}
|
||||
|
||||
public toSourceLike(): SourceLike[] {
|
||||
let result: SourceLike[] = [];
|
||||
|
||||
Object.keys(this.customTypes).forEach((typeName) => {
|
||||
const refinedTypeName = typeName.replace(this.rendererConfig.tsCustomTypePrefix, '');
|
||||
const customDataStructure = new InternalDataStructure(
|
||||
this.rendererConfig,
|
||||
refinedTypeName,
|
||||
this,
|
||||
this.customTypes[typeName].members
|
||||
);
|
||||
result = result.concat(customDataStructure.toSourceCode());
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public transformType(fieldType: ValueType, ignoreNullable = false): string {
|
||||
let targetType: string;
|
||||
const UNKNOWN_TYPE = 'unknown';
|
||||
|
||||
if (this.isBasicTypeKind(fieldType.kind)) {
|
||||
switch (fieldType.kind.value) {
|
||||
case 'string':
|
||||
targetType = 'String';
|
||||
break;
|
||||
case 'number':
|
||||
targetType = 'CGFloat';
|
||||
break;
|
||||
case 'boolean':
|
||||
targetType = 'Bool';
|
||||
break;
|
||||
default:
|
||||
targetType = UNKNOWN_TYPE;
|
||||
}
|
||||
} else if (this.isCustomTypeKind(fieldType.kind)) {
|
||||
targetType = fieldType.kind.name;
|
||||
if (targetType.startsWith(this.rendererConfig.tsCustomTypePrefix)) {
|
||||
targetType = targetType.replace(this.rendererConfig.tsCustomTypePrefix, '');
|
||||
}
|
||||
this.emit(fieldType.kind);
|
||||
} else if (this.isArrayTypeKind(fieldType.kind)) {
|
||||
targetType = `[${this.transformType(fieldType.kind.elementType)}]`;
|
||||
} else {
|
||||
targetType = UNKNOWN_TYPE;
|
||||
}
|
||||
|
||||
if (!ignoreNullable && fieldType.nullable && targetType !== UNKNOWN_TYPE) {
|
||||
targetType += '?';
|
||||
}
|
||||
return targetType;
|
||||
}
|
||||
|
||||
private isBasicTypeKind(kind: ValueType['kind']): kind is BasicTypeKind {
|
||||
return kind.flag === ValueTypeKindFlag.basicType;
|
||||
}
|
||||
|
||||
private isCustomTypeKind(kind: ValueType['kind']): kind is CustomTypeKind {
|
||||
return kind.flag === ValueTypeKindFlag.customType;
|
||||
}
|
||||
|
||||
private isArrayTypeKind(kind: ValueType['kind']): kind is ArrayTypeKind {
|
||||
return kind.flag === ValueTypeKindFlag.arrayType;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
import { SourceLike } from './SourceLike';
|
||||
import { RendererConfig } from './RenderConfig';
|
||||
|
||||
export abstract class GenericCodeRenderer {
|
||||
private sourceLines: SourceLike[] = [];
|
||||
|
||||
private typeLines: SourceLike[] = [];
|
||||
|
||||
namespaces: string[] = [];
|
||||
|
||||
constructor(protected rendererConfig: RendererConfig) {}
|
||||
|
||||
public toSourceCode(): SourceLike[] {
|
||||
this.render();
|
||||
return this.sourceLines;
|
||||
}
|
||||
|
||||
protected abstract render(): void;
|
||||
|
||||
protected resolveEndpoint(moduleName: string): string {
|
||||
return this.rendererConfig.pathMap[moduleName];
|
||||
}
|
||||
|
||||
protected emitLine(indent: number, content: SourceLike): void {
|
||||
const indentString = ' '.repeat(indent);
|
||||
this.sourceLines.push(`${indentString}${content}`);
|
||||
}
|
||||
|
||||
protected emitLines(indent: number, contents: SourceLike[]): void {
|
||||
contents.forEach((content) => this.emitLine(indent, content));
|
||||
}
|
||||
|
||||
protected emitNewLine(): void {
|
||||
this.sourceLines.push('');
|
||||
}
|
||||
|
||||
protected emitCurlyBracketBegin(indent = 0): void {
|
||||
this.emitLine(indent, '{');
|
||||
}
|
||||
|
||||
protected emitCurlyBracketEnd(indent = 0): void {
|
||||
this.emitLine(indent, '}');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { Field } from '../types';
|
||||
import { GenericCodeRenderer } from './GenericCodeRenderer';
|
||||
import { RendererConfig } from './RenderConfig';
|
||||
import { TypeTransformer } from './CustomTypeCollector';
|
||||
|
||||
export class InternalDataStructure extends GenericCodeRenderer {
|
||||
constructor(
|
||||
protected rendererConfig: RendererConfig,
|
||||
private predefinedName: string,
|
||||
private typeTransformer: TypeTransformer,
|
||||
private fields: Field[]
|
||||
) {
|
||||
super(rendererConfig);
|
||||
}
|
||||
|
||||
render(): void {
|
||||
this.emitLine(0, `struct ${this.predefinedName}: Codable {`);
|
||||
this.fields.forEach((field) => {
|
||||
this.emitLine(2, `let ${field.name}: ${this.typeTransformer.transformType(field.type)}`);
|
||||
});
|
||||
this.emitLine(0, `}`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
export interface RendererConfig {
|
||||
globalEntry: string;
|
||||
pathMap: Record<string, string>;
|
||||
tsCustomTypePrefix: string;
|
||||
mergeCustomInterface: boolean;
|
||||
headerTemplate?: string;
|
||||
footerTemplate?: string;
|
||||
makeFunctionPublic?: boolean;
|
||||
baseIndent: number;
|
||||
}
|
||||
|
||||
export class DefaultSwiftRendererConfig implements RendererConfig {
|
||||
'globalEntry' = 'Test';
|
||||
|
||||
'pathMap' = {
|
||||
IHtmlApi: 'htmlApi',
|
||||
IImageOptionApi: 'imageOptionApi',
|
||||
};
|
||||
|
||||
'tsCustomTypePrefix' = 'I';
|
||||
|
||||
'mergeCustomInterface' = true;
|
||||
|
||||
'headerTemplate' = '{';
|
||||
|
||||
'footerTemplate' = '}';
|
||||
|
||||
'makeFunctionPublic' = true;
|
||||
|
||||
'baseIndent' = 2;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
// The idea of this one is originally from:
|
||||
// https://github.com/quicktype/quicktype/blob/master/src/quicktype-core/Source.ts#L65
|
||||
export type SourceLike = string;
|
|
@ -0,0 +1,88 @@
|
|||
import fs from 'fs';
|
||||
import { Module } from '../types';
|
||||
import { RendererConfig } from './RenderConfig';
|
||||
import { InternalDataStructure } from './InternalDataStructure';
|
||||
import { GenericCodeRenderer } from './GenericCodeRenderer';
|
||||
import { TypeTransformer } from './CustomTypeCollector';
|
||||
|
||||
export class SwiftCodeRenderer extends GenericCodeRenderer {
|
||||
constructor(
|
||||
rendererConfig: RendererConfig,
|
||||
private typeTransformer: TypeTransformer,
|
||||
private parsedModules: Module[],
|
||||
private outputPath: string
|
||||
) {
|
||||
super(rendererConfig);
|
||||
}
|
||||
|
||||
render(): void {
|
||||
const apiModules = this.parsedModules;
|
||||
const { baseIndent } = this.rendererConfig;
|
||||
apiModules.forEach((module) => {
|
||||
this.namespaces.push(module.name);
|
||||
this.emitLine(0 + baseIndent, `// ${module.name}`);
|
||||
module.methods.forEach((method) => {
|
||||
const source = `${method.name}`;
|
||||
const parameterSources = method.parameters.map(
|
||||
(parameter) => `${parameter.name}: ${this.typeTransformer.transformType(parameter.type)}`
|
||||
);
|
||||
|
||||
let returnTypeStatement: string;
|
||||
if (method.returnType !== null) {
|
||||
const returnTypeString = this.typeTransformer.transformType(method.returnType, true);
|
||||
|
||||
returnTypeStatement = `@escaping (BridgeCompletion<${returnTypeString}?>)`;
|
||||
} else {
|
||||
returnTypeStatement = `BridgeJSExecutor.Completion? = nil`;
|
||||
}
|
||||
parameterSources.push(`completion: ${returnTypeStatement})`);
|
||||
const parameterSourceStatement = parameterSources.join(', ');
|
||||
|
||||
const modifier = this.rendererConfig.makeFunctionPublic ? 'public ' : '';
|
||||
this.emitLine(0 + baseIndent, `${modifier}func ${source}(${parameterSourceStatement}`);
|
||||
|
||||
this.emitCurlyBracketBegin(baseIndent);
|
||||
|
||||
const hasParam = method.parameters.length > 0;
|
||||
if (hasParam) {
|
||||
const argSource = new InternalDataStructure(
|
||||
this.rendererConfig,
|
||||
'Args',
|
||||
this.typeTransformer,
|
||||
method.parameters
|
||||
).toSourceCode();
|
||||
this.emitLines(2 + baseIndent, argSource);
|
||||
|
||||
this.emitLine(2 + baseIndent, `let args = Args(`);
|
||||
method.parameters.forEach((param) => this.emitLine(4 + baseIndent, `${param.name}: ${param.name}`));
|
||||
this.emitLine(2 + baseIndent, `)`);
|
||||
}
|
||||
const executeMethod = method.returnType !== null ? 'executeFetch' : 'execute';
|
||||
const endpoint = this.resolveEndpoint(module.name);
|
||||
this.emitLine(
|
||||
2 + baseIndent,
|
||||
`jsExecutor.${executeMethod}(with: "${endpoint}", feature: "${method.name}", args: ${
|
||||
hasParam ? 'args' : 'nil'
|
||||
}, completion: completion)`
|
||||
);
|
||||
this.emitCurlyBracketEnd(baseIndent);
|
||||
});
|
||||
});
|
||||
|
||||
if (this.rendererConfig.mergeCustomInterface) {
|
||||
this.emitLines(0 + baseIndent, this.typeTransformer.toSourceLike());
|
||||
}
|
||||
}
|
||||
|
||||
print(): void {
|
||||
let content = this.toSourceCode().join('\n');
|
||||
if (this.rendererConfig.headerTemplate) {
|
||||
content = `${this.rendererConfig.headerTemplate}\n${content}`;
|
||||
}
|
||||
if (this.rendererConfig.footerTemplate) {
|
||||
content = `${content}\n${this.rendererConfig.footerTemplate}`;
|
||||
}
|
||||
fs.writeFileSync(this.outputPath, content);
|
||||
console.log('Generated api has been printed successfully');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
export interface Module {
|
||||
name: string;
|
||||
methods: Method[];
|
||||
}
|
||||
|
||||
export interface Method {
|
||||
name: string;
|
||||
parameters: Field[];
|
||||
returnType: ValueType | null;
|
||||
}
|
||||
|
||||
export interface Field {
|
||||
name: string;
|
||||
type: ValueType;
|
||||
}
|
||||
|
||||
export interface ValueType {
|
||||
kind: ArrayTypeKind | CustomTypeKind | BasicTypeKind;
|
||||
nullable: boolean;
|
||||
}
|
||||
|
||||
export enum ValueTypeKindFlag {
|
||||
basicType = 'basicType',
|
||||
customType = 'customType',
|
||||
arrayType = 'arrayType',
|
||||
}
|
||||
|
||||
interface ValueTypeKind {
|
||||
flag: ValueTypeKindFlag;
|
||||
}
|
||||
|
||||
export interface ArrayTypeKind extends ValueTypeKind {
|
||||
flag: ValueTypeKindFlag.arrayType;
|
||||
elementType: ValueType;
|
||||
}
|
||||
|
||||
export interface CustomTypeKind extends ValueTypeKind {
|
||||
flag: ValueTypeKindFlag.customType;
|
||||
isTypeLiteral?: boolean;
|
||||
name: string;
|
||||
members: Field[];
|
||||
}
|
||||
|
||||
export interface BasicTypeKind extends ValueTypeKind {
|
||||
flag: ValueTypeKindFlag.basicType;
|
||||
value: BasicTypeValue;
|
||||
}
|
||||
|
||||
export enum BasicTypeValue {
|
||||
string = 'string',
|
||||
number = 'number',
|
||||
boolean = 'boolean',
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"include": ["src/*.ts", "src/**/*.ts"],
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"target": "es6",
|
||||
"lib": [
|
||||
"es2019"
|
||||
],
|
||||
"module": "commonjs",
|
||||
"outDir": "dist",
|
||||
"declaration": true,
|
||||
"declarationDir": "typings",
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Загрузка…
Ссылка в новой задаче