idlize
642 строки · 25.0 Кб
1/*
2* Copyright (c) 2024 Huawei Device Co., Ltd.
3* Licensed under the Apache License, Version 2.0 (the "License");
4* you may not use this file except in compliance with the License.
5* You may obtain a copy of the License at
6*
7* http://www.apache.org/licenses/LICENSE-2.0
8*
9* Unless required by applicable law or agreed to in writing, software
10* distributed under the License is distributed on an "AS IS" BASIS,
11* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12* See the License for the specific language governing permissions and
13* limitations under the License.
14*/
15
16import * as ts from 'typescript'17import * as path from 'path'18import { PeerLibrary } from "../PeerLibrary"19import { LanguageWriter, createLanguageWriter, Type, Method, MethodSignature, MethodModifier, NamedMethodSignature } from '../LanguageWriters'20import { mapType } from '../TypeNodeNameConvertor'21import { Language, renameDtsToInterfaces } from '../../util'22import { ImportsCollector } from '../ImportsCollector'23import { EnumEntity, PeerFile } from '../PeerFile'24import { DeclarationConvertor, convertDeclaration } from '../TypeNodeConvertor'25import { TypeNodeConvertor, convertTypeNode } from '../TypeNodeConvertor'26import { IndentedPrinter } from "../../IndentedPrinter"27import { read } from "node:fs";28import { RuntimeType } from '../PeerGeneratorVisitor'29import { TargetFile } from './TargetFile'30import { ARKOALA_PACKAGE_PATH } from '../../lang/java'31
32export class DeclarationGenerator implements DeclarationConvertor<string> {33constructor(34private readonly library: PeerLibrary,35) {}36
37convertClass(node: ts.ClassDeclaration): string {38return this.convertDeclaration(node)39}40
41convertInterface(node: ts.InterfaceDeclaration): string {42return this.convertDeclaration(node)43}44
45convertEnum(node: ts.EnumDeclaration): string {46throw "Enums are processed separately"47}48
49convertTypeAlias(node: ts.TypeAliasDeclaration): string {50const maybeTypeArguments = node.typeParameters?.length51? `<${node.typeParameters.map(it => it.getText()).join(', ')}>`52: ''53let type = mapType(node.type)54return `export declare type ${node.name.text}${maybeTypeArguments} = ${type};`55}56
57private replaceImportTypeNodes(text: string): string {58for (const [stub, src] of [...this.library.importTypesStubToSource.entries()].reverse()) {59text = text.replaceAll(src, stub)60}61return text62}63
64private extendsClause(node: ts.ClassDeclaration | ts.InterfaceDeclaration): string {65if (!node.heritageClauses?.length)66return ``67if (node.heritageClauses!.some(it => it.token !== ts.SyntaxKind.ExtendsKeyword))68throw "Expected to have only extend clauses"69if (this.library.isComponentDeclaration(node))70// do not extend parent component interface to provide smooth integration71return ``72
73let parent = node.heritageClauses[0]!.types[0]74return `extends ${parent.getText()}`75}76
77private declarationName(node: ts.ClassDeclaration | ts.InterfaceDeclaration): string {78let name = ts.idText(node.name as ts.Identifier)79let typeParams = node.typeParameters?.map(it => it.getText()).join(', ')80let typeParamsClause = typeParams ? `<${typeParams}>` : ``81return `${name}${typeParamsClause}`82}83
84private convertDeclaration(node: ts.ClassDeclaration | ts.InterfaceDeclaration): string {85if (!this.library.isComponentDeclaration((node))) {86return 'export ' + this.replaceImportTypeNodes(node.getText())87}88let printer = new IndentedPrinter()89let className = this.declarationName(node)90let extendsClause = this.extendsClause(node)91
92let classOrInterface = ts.isClassDeclaration(node) ? `class` : `interface`93if (this.library.isComponentDeclaration(node))94// because we write `ArkBlank implements BlankAttributes`95classOrInterface = `interface`96printer.print(`export declare ${classOrInterface} ${className} ${extendsClause} {`)97printer.pushIndent()98this.declarationMembers(node)99.forEach(it => {100printer.print(`/** @memo */`)101printer.print(it.getText())102})103printer.popIndent()104printer.print(`}`)105
106return this.replaceImportTypeNodes(printer.getOutput().join('\n'))107}108
109private declarationMembers(110node: ts.ClassDeclaration | ts.InterfaceDeclaration111): readonly (ts.MethodDeclaration)[] {112if (ts.isClassDeclaration(node)) {113const members = node.members.filter(it => !ts.isConstructorDeclaration(it))114if (members.every(ts.isMethodDeclaration))115return members116}117if (ts.isInterfaceDeclaration(node) ) {118const members = node.members.filter(it =>119!ts.isConstructSignatureDeclaration(it) &&120!ts.isCallSignatureDeclaration(it))121if (members.length === 0)122return []123}124throw new Error(`Encountered component with member that is not method: ${node}`)125}126}
127
128interface InterfacesVisitor {129getInterfaces(): Map<TargetFile, LanguageWriter>130printInterfaces(): void131}
132
133class TSInterfacesVisitor implements InterfacesVisitor {134protected readonly interfaces: Map<TargetFile, LanguageWriter> = new Map()135protected readonly generator: DeclarationGenerator136
137constructor(138protected readonly peerLibrary: PeerLibrary,139) {140this.generator = new DeclarationGenerator(peerLibrary)141}142
143protected generateFileBasename(originalFilename: string): string {144return renameDtsToInterfaces(path.basename(originalFilename), this.peerLibrary.declarationTable.language)145}146
147private printImports(writer: LanguageWriter, file: PeerFile) {148const imports = new ImportsCollector()149imports.addFilterByBasename(this.generateFileBasename(file.originalFilename))150file.importFeatures.forEach(it => imports.addFeature(it.feature, it.module))151imports.print(writer)152}153
154protected printEnum(writer: LanguageWriter, enumEntity: EnumEntity) {155writer.print(enumEntity.comment)156writer.print(`export enum ${enumEntity.name} {`)157writer.pushIndent()158enumEntity.members.forEach((member, index) => {159writer.print(member.comment)160const commaOp = index < enumEntity.members.length - 1 ? ',' : ''161if (member.initializerText != undefined) {162writer.print(`${member.name} = ${member.initializerText}${commaOp}`)163} else {164writer.print(`${member.name}${commaOp}`)165}166})167writer.popIndent()168writer.print(`}`)169}170
171private printAssignEnumsToGlobalScope(writer: LanguageWriter, peerFile: PeerFile) {172if (![Language.TS, Language.ARKTS].includes(writer.language)) return173if (peerFile.enums.length != 0) {174writer.print(`Object.assign(globalThis, {`)175writer.pushIndent()176for (const enumEntity of peerFile.enums) {177writer.print(`${enumEntity.name}: ${enumEntity.name},`)178}179writer.popIndent()180writer.print(`})`)181}182}183
184getInterfaces(): Map<TargetFile, LanguageWriter> {185return this.interfaces186}187
188printInterfaces() {189for (const file of this.peerLibrary.files.values()) {190const writer = createLanguageWriter(Language.TS)191
192this.printImports(writer, file)193file.declarations.forEach(it => writer.print(convertDeclaration(this.generator, it)))194file.enums.forEach(it => this.printEnum(writer, it))195this.printAssignEnumsToGlobalScope(writer, file)196this.interfaces.set(new TargetFile(this.generateFileBasename(file.originalFilename)), writer)197}198}199}
200
201export class JavaTypeNodeNameConvertor implements202TypeNodeConvertor<string>203{
204convertUnion(node: ts.UnionTypeNode): string {205let unionType = 'Union'206for (const unionSubtype of node.types) {207unionType = `${unionType}_${this.convert(unionSubtype)}`208}209return unionType210}211convertTypeLiteral(node: ts.TypeLiteralNode): string {212const members = node.members.map(it => {213if (ts.isPropertySignature(it)) {214const name = this.convert(it.name)215const isOptional = !!it.questionToken216const type = this.convert(it.type!)217if (isOptional) {218return `Opt_${type} ${name}`219}220return `${type} ${name}`221}222if (ts.isIndexSignatureDeclaration(it)) {223if (it.modifiers) throw new Error('Not implemented')224if (it.typeParameters) throw new Error('Not implemented')225if (it.questionToken) throw new Error('Not implemented')226if (it.name) throw new Error('Not implemented')227const parameters = it.parameters.map(it => this.convertParameterDeclaration(it))228return `[${parameters.join(', ')}]: ${this.convert(it.type)}`229}230throw new Error(`Unknown member type ${ts.SyntaxKind[it.kind]}`)231})232return `{${members.join(', ')}}`233}234private convertParameterDeclaration(node: ts.ParameterDeclaration): string {235if (node.modifiers) throw new Error('Not implemented')236if (!node.type) throw new Error('Expected ParameterDeclaration to have a type')237const isOptional = !!node.questionToken238const name = this.convert(node.name)239const type = this.convert(node.type!)240if (isOptional) {241return `Opt_${type}$ ${name}`242}243return `${type} ${name}`244}245convertLiteralType(node: ts.LiteralTypeNode): string {246if (node.literal.kind === ts.SyntaxKind.TrueKeyword) return 'true'247if (node.literal.kind === ts.SyntaxKind.FalseKeyword) return 'false'248if (node.literal.kind === ts.SyntaxKind.NullKeyword) return 'null'249if (node.literal.kind === ts.SyntaxKind.StringLiteral) return `"${node.literal.text}"`250throw new Error(`Unknown LiteralTypeNode ${ts.SyntaxKind[node.literal.kind]}`)251}252convertTuple(node: ts.TupleTypeNode): string {253const members = node.elements.map(it => this.convertTupleElement(it))254return `Tuple_${members.join('_')}`255}256protected convertTupleElement(node: ts.TypeNode): string {257if (ts.isNamedTupleMember(node)) {258const name = this.convert(node.name)259const maybeQuestion = node.questionToken ? '?' : ''260const type = this.convert(node.type!)261return `${name}${maybeQuestion}: ${type}`262}263return this.convert(node)264}265convertArray(node: ts.ArrayTypeNode): string {266return `${this.convert(node.elementType)}[]`267}268convertOptional(node: ts.OptionalTypeNode): string {269return `Opt_${this.convert(node.type)}`270}271convertFunction(node: ts.FunctionTypeNode): string {272if (node.typeParameters?.length)273throw new Error('Not implemented')274const parameters = node.parameters.map(it => {275const name = this.convert(it.name)276const maybeQuestion = it.questionToken ? '?' : ''277const type = this.convert(it.type!)278return `${name}${maybeQuestion}: ${type}`279})280return `((${parameters.join(', ')}) => ${this.convert(node.type)})`281}282convertTemplateLiteral(node: ts.TemplateLiteralTypeNode): string {283return node.templateSpans.map(template => {284return `\`\${${this.convert(template.type)}}${template.literal.rawText}\``285}).join()286}287convertImport(node: ts.ImportTypeNode): string {288const from = this.convert(node.argument)289const qualifier = this.convert(node.qualifier!)290const maybeTypeArguments = node.typeArguments?.length291? '_' + node.typeArguments.map(it => this.convert(it)).join('_')292: ''293return `IMPORT_${qualifier}${maybeTypeArguments}_FROM_${from}`294.match(/[a-zA-Z]+/g)!.join('_')295}296convertTypeReference(node: ts.TypeReferenceNode): string {297const name = this.convert(node.typeName)298if (name === 'Style')299return this.convert(ts.factory.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword))300let types = node.typeArguments?.map(it => this.convert(it))301if (name === `AttributeModifier`)302types = [`object`]303if (name === `ContentModifier`)304types = [this.convert(ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword))]305if (name === `Optional`)306return `${types} | undefined`307const maybeTypeArguments = !types?.length ? '' : `<${types.join(', ')}>`308return `${name}${maybeTypeArguments}`309}310convertParenthesized(node: ts.ParenthesizedTypeNode): string {311return `(${this.convert(node.type)})`312}313convertIndexedAccess(node: ts.IndexedAccessTypeNode): string {314throw new Error('Method not implemented.')315}316convertTypeParameterDeclaration(node: ts.TypeParameterDeclaration): string {317throw new Error('Method not implemented.')318}319convertStringKeyword(node: ts.TypeNode): string {320return 'String'321}322convertNumberKeyword(node: ts.TypeNode): string {323return 'Float'324}325convertBooleanKeyword(node: ts.TypeNode): string {326return 'Boolean'327}328convertUndefinedKeyword(node: ts.TypeNode): string {329return 'Undefined'330}331convertVoidKeyword(node: ts.TypeNode): string {332return 'void'333}334convertObjectKeyword(node: ts.TypeNode): string {335return 'Object'336}337convertAnyKeyword(node: ts.TypeNode): string {338return 'any'339}340convertUnknownKeyword(node: ts.TypeNode): string {341return `unknown`342}343
344// identifier345convertQualifiedName(node: ts.QualifiedName): string {346return `${this.convert(node.left)}.${this.convert(node.right)}`347}348convertIdentifier(node: ts.Identifier): string {349return node.text350}351
352convert(node: ts.Node): string {353if (ts.isQualifiedName(node)) return this.convertQualifiedName(node)354if (ts.isIdentifier(node)) return this.convertIdentifier(node)355if (ts.isTypeNode(node))356return convertTypeNode(this, node)357throw new Error(`Unknown node type ${ts.SyntaxKind[node.kind]}`)358}359}
360
361const nameConvertorInstance = new JavaTypeNodeNameConvertor()362
363// will be refactored after migration to IDL IR
364function mapTypeJava(type: ts.TypeNode | undefined): string {365type ??= ts.factory.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword)366return nameConvertorInstance.convert(type)367}
368
369class JavaInterfacesVisitor {370private readonly interfaces: Map<string, LanguageWriter> = new Map()371
372constructor(373private readonly peerLibrary: PeerLibrary,374) {}375
376addInterface(name: string, writer: LanguageWriter) {377this.interfaces.set(name, writer)378}379
380hasInterface(name: string): boolean {381return this.interfaces.has(name)382}383
384getName(node: ts.NamedDeclaration): string {385if (!node.name) {386throw new Error(`Empty name for node\n${node}`)387}388return node.name.getText()389}390
391getMemberTargetType(node: ts.PropertyDeclaration | ts.PropertySignature): Type {392const nullable = !!node.questionToken393const type = mapTypeJava(node.type)394if (nullable) {395return new Type(`Opt_${type}`, true)396}397return new Type(type, false)398}399
400getSuperClass(node: ts.ClassDeclaration | ts.InterfaceDeclaration): string | undefined {401if (!node.heritageClauses) {402return403}404
405for (const clause of node.heritageClauses) {406if (clause.token == ts.SyntaxKind.ExtendsKeyword) {407return clause.types[0].expression.getText()408}409}410}411
412printPackage(writer: LanguageWriter): void {413writer.print("package org.koalaui.arkoala;\n")414}415
416implementType(sourceType: ts.TypeNode | undefined, targetType: Type) {417if (targetType.nullable) {418throw new Error('Types for optional class/interface members must be implemented using implementOptionalMemberType');419}420
421if (!sourceType) {422return423}424if (this.hasInterface(targetType.name)) {425return426}427
428if (ts.isUnionTypeNode(sourceType)) {429const writer = createLanguageWriter(Language.JAVA)430this.printPackage(writer)431this.printUnionImplementation(sourceType, targetType, writer)432this.addInterface(targetType.name, writer)433return434}435if (ts.isTupleTypeNode(sourceType)) {436const writer = createLanguageWriter(Language.JAVA)437this.printPackage(writer)438this.printTupleImplementation(sourceType, targetType, writer)439this.addInterface(targetType.name, writer)440return441}442if (ts.isOptionalTypeNode(sourceType)) {443const writer = createLanguageWriter(Language.JAVA)444this.printPackage(writer)445this.printOptionalTypeImplementation(sourceType, targetType, writer)446this.addInterface(targetType.name, writer)447return448}449}450
451implementOptionalMemberType(sourceType: ts.TypeNode | undefined, targetType: Type) {452if (!sourceType) {453return454}455if (this.hasInterface(targetType.name)) {456return457}458
459const writer = createLanguageWriter(Language.JAVA)460this.printPackage(writer)461this.printOptionalImplementation(sourceType, targetType, writer)462this.addInterface(targetType.name, writer)463}464
465printOptionalTypeImplementation(sourceType: ts.OptionalTypeNode, targetType: Type, writer: LanguageWriter) {466this.printOptionalImplementation(sourceType.type, targetType, writer)467}468
469private printOptionalImplementation(sourceType: ts.TypeNode, targetType: Type, writer: LanguageWriter) {470writer.writeClass(targetType.name, () => {471const tag = 'tag'472const value = 'value'473const rtType = new Type('RuntimeType')474writer.writeMethodImplementation(new Method('getRuntimeType', new MethodSignature(rtType, []), [MethodModifier.PUBLIC]), () => {475writer.writeStatement(476writer.makeReturn(477writer.makeTernary(478writer.makeString(`${tag} == ${writer.makeTag('UNDEFINED')}`),479writer.makeString('RuntimeType.UNDEFINED'),480writer.makeString('RuntimeType.OBJECT')481)482)483)484})485
486writer.writeFieldDeclaration('tag', new Type('Tag'), ['public'], false)487
488const targetType = new Type(mapTypeJava(sourceType))489this.implementType(sourceType, targetType)490writer.writeFieldDeclaration(value, targetType, ['public'], false)491})492}493
494printUnionImplementation(sourceType: ts.UnionTypeNode, targetType: Type, writer: LanguageWriter) {495writer.writeClass(targetType.name, () => {496const intType = new Type('int')497const selector = 'selector'498writer.writeFieldDeclaration(selector, intType, ['private'], false)499writer.writeMethodImplementation(new Method('getSelector', new MethodSignature(intType, []), [MethodModifier.PUBLIC]), () => {500writer.writeStatement(501writer.makeReturn(502writer.makeString(selector)503)504)505})506
507for (const [index, subType] of sourceType.types.entries()) {508const subTypeTargetType = new Type(mapTypeJava(subType))509this.implementType(subType, subTypeTargetType)510const value = `value${index}`511const param = 'param'512
513writer.writeFieldDeclaration(value, subTypeTargetType, ['private'], false)514
515writer.writeConstructorImplementation(516targetType.name,517new NamedMethodSignature(Type.Void, [subTypeTargetType], [param]),518() => {519writer.writeStatement(520writer.makeAssign(value, undefined, writer.makeString(param), false, false)521)522writer.writeStatement(523writer.makeAssign(selector, undefined, writer.makeString(index.toString()), false, false)524)525}526)527
528writer.writeMethodImplementation(529new Method(`getValue${index}`, new MethodSignature(subTypeTargetType, []), [MethodModifier.PUBLIC]),530() => {531writer.writeStatement(532writer.makeReturn(533writer.makeString(value)534)535)536}537)538}539})540}541
542printTupleImplementation(sourceType: ts.TupleTypeNode, targetType: Type, writer: LanguageWriter) {543writer.writeClass(targetType.name, () => {544const rtType = new Type('RuntimeType')545writer.writeMethodImplementation(new Method('getRuntimeType', new MethodSignature(rtType, []), [MethodModifier.PUBLIC]), () => {546writer.writeStatement(547writer.makeReturn(548writer.makeString('RuntimeType.OBJECT')549)550)551})552
553for (const [index, subType] of sourceType.elements.entries()) {554const subTypeTargetType = new Type(mapTypeJava(subType))555this.implementType(subType, subTypeTargetType)556const value = `value${index}`557
558writer.writeFieldDeclaration(value, subTypeTargetType, ['public'], false)559}560})561}562
563printClassOrInterface(node: ts.ClassDeclaration | ts.InterfaceDeclaration, writer: LanguageWriter) {564const superClass = this.getSuperClass(node)565writer.writeClass(this.getName(node), () => {566for (const member of node.members) {567if (!ts.isPropertyDeclaration(member) && !ts.isPropertySignature(member)) {568continue569}570
571const propertyName = this.getName(member)572const propertyType = this.getMemberTargetType(member)573writer.writeFieldDeclaration(propertyName, propertyType, ['public'], false)574
575if (propertyType.nullable) {576this.implementOptionalMemberType(member.type, propertyType)577continue578}579this.implementType(member.type, propertyType)580}581}, superClass)582}583
584getInterfaces(): Map<TargetFile, LanguageWriter> {585const result = new Map<TargetFile, LanguageWriter>()586for (const [name, writer] of this.interfaces) {587result.set(new TargetFile(name, ARKOALA_PACKAGE_PATH), writer)588}589return result590}591
592printInterfaces() {593for (const file of this.peerLibrary.files.values()) {594file.declarations.forEach(it => {595if (!ts.isClassDeclaration(it) && !ts.isInterfaceDeclaration(it)) {596return597}598const writer = createLanguageWriter(Language.JAVA)599this.printPackage(writer);600this.printClassOrInterface(it, writer)601this.addInterface(this.getName(it), writer)602})603}604}605}
606
607class ArkTSInterfacesVisitor extends TSInterfacesVisitor {608override printInterfaces() {609for (const file of this.peerLibrary.files.values()) {610const writer = createLanguageWriter(Language.ARKTS)611file.enums.forEach(it => this.printEnum(writer, it))612this.interfaces.set(new TargetFile(this.generateFileBasename(file.originalFilename)), writer)613}614}615}
616
617function getVisitor(peerLibrary: PeerLibrary, lang: Language): InterfacesVisitor | undefined {618if (lang == Language.TS) {619return new TSInterfacesVisitor(peerLibrary)620}621if (lang == Language.JAVA) {622return new JavaInterfacesVisitor(peerLibrary)623}624if (lang == Language.ARKTS) {625return new ArkTSInterfacesVisitor(peerLibrary)626}627}
628
629export function printInterfaces(peerLibrary: PeerLibrary, lang: Language): Map<TargetFile, string> {630const visitor = getVisitor(peerLibrary, lang)631if (!visitor) {632return new Map()633}634
635visitor.printInterfaces()636const result = new Map<TargetFile, string>()637for (const [key, writer] of visitor.getInterfaces()) {638if (writer.getOutput().length === 0) continue639result.set(key, writer.getOutput().join('\n'))640}641return result642}
643