idlize
901 строка · 37.9 Кб
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 {18asString,19capitalize,20identName,21nameOrNull,22serializerBaseMethods,23className,24isDefined,25isStatic,26throwException,27getComment,28isReadonly,29getDeclarationsByNode,30Language
31} from "../util"32import { GenericVisitor } from "../options"33import {34ArgConvertor, RetConvertor,35} from "./Convertors"36import { PeerGeneratorConfig } from "./PeerGeneratorConfig";37import { DeclarationTable, PrimitiveType } from "./DeclarationTable"38import {39singleParentDeclaration,40} from "./inheritance"41import { PeerClass } from "./PeerClass"42import { PeerMethod } from "./PeerMethod"43import { PeerFile, EnumEntity } from "./PeerFile"44import { PeerLibrary } from "./PeerLibrary"45import { MaterializedClass, MaterializedField, MaterializedMethod, SuperElement, checkTSDeclarationMaterialized, isMaterialized } from "./Materialized"46import { Field, FieldModifier, Method, MethodModifier, NamedMethodSignature, Type } from "./LanguageWriters";47import { mapType } from "./TypeNodeNameConvertor";48import { convertDeclaration, convertTypeNode } from "./TypeNodeConvertor";49import { DeclarationDependenciesCollector, TypeDependenciesCollector } from "./dependencies_collector";50import { convertDeclToFeature } from "./ImportsCollector";51import { addSyntheticDeclarationDependency, isSyntheticDeclaration, makeSyntheticTypeAliasDeclaration } from "./synthetic_declaration";52import { isBuilderClass, isCustomBuilderClass, toBuilderClass } from "./BuilderClass";53
54export enum RuntimeType {55UNEXPECTED = -1,56NUMBER = 1,57STRING = 2,58OBJECT = 3,59BOOLEAN = 4,60UNDEFINED = 5,61BIGINT = 6,62FUNCTION = 7,63SYMBOL = 8,64MATERIALIZED = 9,65}
66
67/**
68* Theory of operations.
69*
70* We use type definition as "grammar", and perform recursive descent to terminal nodes of such grammar
71* generating serialization code. We use TS typechecker to analyze compound and union types and generate
72* universal finite automata to serialize any value of the given type.
73*/
74
75
76export interface TypeAndName {77type: ts.TypeNode78name: string79optional: boolean80}
81
82export type PeerGeneratorVisitorOptions = {83sourceFile: ts.SourceFile84typeChecker: ts.TypeChecker85declarationTable: DeclarationTable,86peerLibrary: PeerLibrary87}
88
89export class ComponentDeclaration {90constructor(91public readonly name: string,92public readonly interfaceDeclaration: ts.InterfaceDeclaration | undefined,93public readonly attributesDeclarations: ts.ClassDeclaration,94) {}95}
96
97function isSubclass(typeChecker: ts.TypeChecker, node: ts.ClassDeclaration, maybeParent: ts.ClassDeclaration): boolean {98const heritageParentType = node.heritageClauses?.[0].types[0].expression99const heritageDeclarations = heritageParentType ? getDeclarationsByNode(typeChecker, heritageParentType) : []100return heritageDeclarations.some(it => {101if (it === maybeParent)102return true103if (ts.isClassDeclaration(it))104return isSubclass(typeChecker, it, maybeParent)105return false106})107}
108
109function isSubclassComponent(typeChecker: ts.TypeChecker, a: ComponentDeclaration, b: ComponentDeclaration) {110return isSubclass(typeChecker, a.attributesDeclarations, b.attributesDeclarations)111}
112
113export class PeerGeneratorVisitor implements GenericVisitor<void> {114private readonly sourceFile: ts.SourceFile115declarationTable: DeclarationTable116
117static readonly serializerBaseMethods = serializerBaseMethods()118readonly typeChecker: ts.TypeChecker119
120readonly peerLibrary: PeerLibrary121readonly peerFile: PeerFile122
123constructor(options: PeerGeneratorVisitorOptions) {124this.sourceFile = options.sourceFile125this.typeChecker = options.typeChecker126this.declarationTable = options.declarationTable127this.peerLibrary = options.peerLibrary128this.peerFile = new PeerFile(this.sourceFile.fileName, this.declarationTable, this.peerLibrary.componentsToGenerate)129this.peerLibrary.files.push(this.peerFile)130}131
132visitWholeFile(): void {133ts.forEachChild(this.sourceFile, (node) => this.visit(node))134}135
136visit(node: ts.Node) {137if (ts.isVariableStatement(node)) {138this.processVariableStatement(node)139} else if (ts.isModuleDeclaration(node)) {140if (node.body && ts.isModuleBlock(node.body)) {141node.body.statements.forEach(it => this.visit(it))142}143} else if (ts.isClassDeclaration(node) ||144ts.isInterfaceDeclaration(node) ||145ts.isEnumDeclaration(node) ||146ts.isVariableStatement(node) ||147ts.isExportDeclaration(node) ||148ts.isTypeAliasDeclaration(node) ||149ts.isFunctionDeclaration(node) ||150ts.isEmptyStatement(node) ||151ts.isImportDeclaration(node) ||152node.kind == ts.SyntaxKind.EndOfFileToken) {153// Do nothing.154} else {155throw new Error(`Unknown node: ${node.kind} ${node.getText()}`)156}157}158
159private processVariableStatement(node: ts.VariableStatement) {160node.declarationList.declarations.forEach(variable => {161const interfaceDecl = this.maybeTypeReferenceToDeclaration(variable.type)162if (!interfaceDecl || !ts.isInterfaceDeclaration(interfaceDecl))163return164const attributesDecl = this.interfaceToComponentAttributes(interfaceDecl)165if (attributesDecl) {166if (this.peerLibrary.isComponentDeclaration(interfaceDecl) ||167this.peerLibrary.isComponentDeclaration(attributesDecl))168throw new Error("Component is already defined")169const componentName = identName(variable.name)!170if (PeerGeneratorConfig.ignoreComponents.includes(componentName))171return172this.peerLibrary.componentsDeclarations.push(new ComponentDeclaration(173componentName,174interfaceDecl,175attributesDecl,176))177}178})179}180
181private maybeTypeReferenceToDeclaration(node: ts.TypeNode | undefined): ts.Declaration | undefined {182if (!node || !ts.isTypeReferenceNode(node))183return undefined184return getDeclarationsByNode(this.typeChecker, node.typeName)?.[0]185}186
187private interfaceToComponentAttributes(node: ts.InterfaceDeclaration | undefined): ts.ClassDeclaration | undefined {188if (!node)189return undefined190const members = node.members.filter(it => !ts.isConstructSignatureDeclaration(it))191if (!members.length || !members.every(it => ts.isCallSignatureDeclaration(it)))192return undefined193const callable = members[0] as ts.CallSignatureDeclaration194const retDecl = this.maybeTypeReferenceToDeclaration(callable.type)195const isSameReturnType = (node: ts.TypeElement): boolean => {196if (!ts.isCallSignatureDeclaration(node))197throw "Expected to be a call signature"198const otherRetDecl = this.maybeTypeReferenceToDeclaration(node.type)199return otherRetDecl === retDecl200}201
202if (!retDecl || !ts.isClassDeclaration(retDecl) || !members.every(isSameReturnType))203return undefined204
205return retDecl206}207
208private processCustomComponent(node: ts.ClassDeclaration) {209const methods = node.members210.filter(it => ts.isMethodDeclaration(it) || ts.isMethodSignature(it))211.map(it => it.getText().replace(/;\s*$/g, ''))212.map(it => `${it} { throw new Error("not implemented"); }`)213this.peerLibrary.customComponentMethods.push(...methods)214}215}
216
217function tempExtractParameters(method: ts.ConstructorDeclaration | ts.MethodDeclaration | ts.MethodSignature | ts.CallSignatureDeclaration): ts.ParameterDeclaration[] {218if (!ts.isCallSignatureDeclaration(method) && identName(method.name) === "onWillScroll") {219/**220* ScrollableCommonMethod has a method `onWillScroll(handler: Optional<OnWillScrollCallback>): T;`
221* ScrollAttribute extends ScrollableCommonMethod and overrides this method as
222* `onWillScroll(handler: ScrollOnWillScrollCallback): ScrollAttribute;`. So that override is not
223* valid and cannot be correctly processed and we want to stub this for now.
224*/
225return [{226...ts.factory.createParameterDeclaration(227undefined,228undefined,229"stub_for_onWillScroll",230undefined,231{232...ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),233getText: () => "any"234},235),236getText: () => "stub_for_onWillScroll: any",237}]238}239return Array.from(method.parameters)240}
241
242export function generateSignature(method: ts.ConstructorDeclaration | ts.MethodDeclaration | ts.MethodSignature | ts.CallSignatureDeclaration): NamedMethodSignature {243const parameters = tempExtractParameters(method)244const returnName = identName(method.type)!245const returnType = returnName === "void" ? Type.Void246: isStatic(method.modifiers) ? new Type(returnName) : Type.This247return new NamedMethodSignature(returnType,248parameters
249.map(it => new Type(mapType(it.type), it.questionToken != undefined)),250parameters
251.map(it => identName(it.name)!),252)253}
254
255function generateArgConvertor(table: DeclarationTable, param: ts.ParameterDeclaration): ArgConvertor {256if (!param.type) throw new Error("Type is needed")257let paramName = asString(param.name)258let optional = param.questionToken !== undefined259return table.typeConvertor(paramName, param.type, optional)260}
261
262function generateRetConvertor(typeNode?: ts.TypeNode): RetConvertor {263let nativeType = typeNode ? mapCInteropRetType(typeNode) : "void"264let isVoid = nativeType == "void"265return {266isVoid: isVoid,267nativeType: () => nativeType,268macroSuffixPart: () => isVoid ? "V" : ""269}270}
271
272function mapCInteropRetType(type: ts.TypeNode): string {273if (type.kind == ts.SyntaxKind.VoidKeyword) {274return `void`275}276if (type.kind == ts.SyntaxKind.NumberKeyword) {277return PrimitiveType.Int32.getText()278}279if (type.kind == ts.SyntaxKind.BooleanKeyword) {280return PrimitiveType.Boolean.getText()281}282if (ts.isTypeReferenceNode(type)) {283let name = identName(type.typeName)!284/* HACK, fix */285if (name.endsWith("Attribute")) return "void"286switch (name) {287/* ANOTHER HACK, fix */288case "T": return "void"289case "UIContext": return PrimitiveType.NativePointer.getText()290default: return PrimitiveType.NativePointer.getText()291}292}293if (type.kind == ts.SyntaxKind.StringKeyword) {294/* HACK, fix */295// return `KStringPtr`296return "void"297}298if (ts.isUnionTypeNode(type)) {299console.log(`WARNING: unhandled union type: ${type.getText()}`)300// TODO: not really properly supported.301if (type.types[0].kind == ts.SyntaxKind.VoidKeyword) return "void"302if (type.types.length == 2) {303if (type.types[1].kind == ts.SyntaxKind.UndefinedKeyword) return `void`304if (ts.isLiteralTypeNode(type.types[1]) && type.types[1].literal.kind == ts.SyntaxKind.NullKeyword) {305// NavPathStack | null306return mapCInteropRetType(type.types[0])307}308}309// TODO: return just type of the first elem310// for the materialized class getter with union type311return mapCInteropRetType(type.types[0])312}313if (ts.isArrayTypeNode(type)) {314/* HACK, fix */315// return array by some way316return "void"317}318if (ts.isParenthesizedTypeNode(type)) {319return `(${mapCInteropRetType(type.type)})`320}321throw new Error(type.getText())322}
323
324
325class ImportsAggregateCollector extends TypeDependenciesCollector {326constructor(327private readonly peerLibrary: PeerLibrary,328private readonly expandAliases: boolean,329) {330super(peerLibrary.declarationTable.typeChecker!)331}332
333override convertImport(node: ts.ImportTypeNode): ts.Declaration[] {334const generatedName = mapType(node)335if (!this.peerLibrary.importTypesStubToSource.has(generatedName)) {336this.peerLibrary.importTypesStubToSource.set(generatedName, node.getText())337}338let syntheticDeclaration: ts.Declaration339
340if (node.qualifier?.getText() === 'Resource') {341syntheticDeclaration = makeSyntheticTypeAliasDeclaration(342'SyntheticDeclarations',343generatedName,344ts.factory.createTypeReferenceNode("ArkResource"),345)346addSyntheticDeclarationDependency(syntheticDeclaration, {feature: "ArkResource", module: "./ArkResource"})347} else {348syntheticDeclaration = makeSyntheticTypeAliasDeclaration(349'SyntheticDeclarations',350generatedName,351ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),352)353}354return [355...super.convertImport(node),356syntheticDeclaration
357]358}359
360override convertTypeReference(node: ts.TypeReferenceNode): ts.Declaration[] {361const declarations = super.convertTypeReference(node)362const result = [...declarations]363for (const decl of declarations) {364// expand type aliaces because we have serialization inside peers methods365if (this.expandAliases && ts.isTypeAliasDeclaration(decl)) {366result.push(...this.convert(decl.type))367}368}369return result370}371}
372
373class FilteredDeclarationCollector extends DeclarationDependenciesCollector {374constructor(375private readonly library: PeerLibrary,376typeDepsCollector: TypeDependenciesCollector,377) {378super(library.declarationTable.typeChecker!, typeDepsCollector)379}380
381protected override convertHeritageClause(clause: ts.HeritageClause): ts.Declaration[] {382const parent = clause.parent383if (ts.isClassDeclaration(parent) && this.library.isComponentDeclaration(parent)) {384return []385}386return super.convertHeritageClause(clause)387}388}
389
390class ComponentsCompleter {391constructor(392private readonly library: PeerLibrary,393) {}394
395private componentNameByClass(node: ts.ClassDeclaration): string {396return node.name!.text397}398
399public process(): void {400for (let i = 0; i < this.library.componentsDeclarations.length; i++) {401const attributes = this.library.componentsDeclarations[i].attributesDeclarations402if ((attributes.heritageClauses?.length ?? 0) > 1)403throw new Error("Expected component attributes to have single heritage clause at most")404const heritage = attributes.heritageClauses?.[0]405if (!heritage)406continue407const parentDecls = getDeclarationsByNode(this.library.declarationTable.typeChecker!, heritage.types[0].expression)408// to resolve a problem with duplicate CommonMethod interface in koala fakes409.filter(it => ts.isClassDeclaration(it))410if (parentDecls.length !== 1)411throw new Error("Expected parent to have single declaration")412const parentDecl = parentDecls[0]413if (!ts.isClassDeclaration(parentDecl))414throw new Error("Expected parent to be a class")415if (!this.library.isComponentDeclaration(parentDecl)) {416this.library.componentsDeclarations.push(new ComponentDeclaration(417this.componentNameByClass(parentDecl),418undefined,419parentDecl,420))421}422}423// topological sort424const components = this.library.componentsDeclarations425for (let i = 0; i < components.length; i++) {426for (let j = i + 1; j < components.length; j++) {427if (isSubclassComponent(this.library.declarationTable.typeChecker!, components[i], components[j])) {428components.splice(i, 0, ...components.splice(j, 1))429i--430break431}432}433}434}435}
436
437class PeersGenerator {438constructor(439private readonly library: PeerLibrary,440) {}441
442private get declarationTable(): DeclarationTable {443return this.library.declarationTable444}445
446private extractMethods(node: ts.ClassDeclaration | ts.InterfaceDeclaration): (ts.MethodDeclaration | ts.CallSignatureDeclaration)[] {447return (node.members as ts.NodeArray<ts.Node>).filter(448it => (ts.isMethodDeclaration(it) || ts.isCallSignatureDeclaration(it))449) as (ts.MethodDeclaration | ts.CallSignatureDeclaration)[]450}451
452private processMethodOrCallable(453method: ts.MethodDeclaration | ts.CallSignatureDeclaration,454peer: PeerClass,455parentName?: string456): PeerMethod | undefined {457const isCallSignature = ts.isCallSignatureDeclaration(method)458// Some method have other parents as part of their names459// Such as the ones coming from thr friend interfaces460// E.g. ButtonInterface instead of ButtonAttribute461const originalParentName = parentName ?? peer.originalClassName!462const methodName = isCallSignature ? `_set${peer.componentName}Options` : identName(method.name)!463
464if (PeerGeneratorConfig.ignorePeerMethod.includes(methodName)) return465
466this.declarationTable.setCurrentContext(`${originalParentName}.${methodName}()`)467
468// TODO: fix this ugly code to prevent method args aliases name collisions.469let methodIndex = 0, index = 0470let clazz = method.parent471if (ts.isClassDeclaration(clazz) || ts.isInterfaceDeclaration(clazz)) {472clazz.members.forEach(it => {473if (((ts.isMethodDeclaration(it) && identName(it.name) == methodName) || ts.isCallSignatureDeclaration(it))) {474if (method == it) methodIndex = index475index++476}477})478}479
480const parameters = tempExtractParameters(method)481parameters.forEach((param, index) => {482if (param.type) {483this.declarationTable.requestType(484`Type_${originalParentName}_${methodName}${methodIndex == 0 ? "" : methodIndex.toString()}_Arg${index}`,485param.type,486this.library.shouldGenerateComponent(peer.componentName),487)488}489})490const argConvertors = parameters491.map((param) => generateArgConvertor(this.declarationTable, param))492const declarationTargets = parameters493.map((param) => this.declarationTable.toTarget(param.type ??494throwException(`Expected a type for ${asString(param)} in ${asString(method)}`)))495const retConvertor = generateRetConvertor(method.type)496
497// TODO: restore collapsing logic!498const signature = /* collapsed?.signature ?? */ generateSignature(method)499
500const peerMethod = new PeerMethod(501originalParentName,502declarationTargets,503argConvertors,504retConvertor,505isCallSignature,506false,507new Method(methodName, signature, isStatic(method.modifiers) ? [MethodModifier.STATIC] : []),508)509this.declarationTable.setCurrentContext(undefined)510return peerMethod511}512
513private createComponentAttributesDeclaration(node: ts.ClassDeclaration, peer: PeerClass): void {514if (PeerGeneratorConfig.invalidAttributes.includes(peer.componentName)) {515return516}517const seenAttributes = new Set<string>()518node.members.forEach(child => {519if (ts.isMethodDeclaration(child)) {520this.processOptionAttribute(seenAttributes, child, peer)521}522})523}524
525private processOptionAttribute(seenAttributes: Set<string>, method: ts.MethodDeclaration | ts.MethodSignature, peer: PeerClass): void {526const methodName = method.name.getText()527if (seenAttributes.has(methodName)) {528console.log(`WARNING: ignore seen method: ${methodName}`)529return530}531const parameters = tempExtractParameters(method)532if (parameters.length != 1) {533// We only convert one argument methods to attributes.534return535}536seenAttributes.add(methodName)537const type = this.argumentType(methodName, parameters, peer)538peer.attributesFields.push(`${methodName}?: ${type}`)539}540
541private argumentType(methodName: string, parameters: ts.ParameterDeclaration[], peer: PeerClass): string {542const argumentTypeName = capitalize(methodName) + "ValuesType"543if (parameters.length === 1 && ts.isTypeLiteralNode(parameters[0].type!)) {544const typeLiteralStatements = parameters[0].type!.members545.map(it => {546// TODO: properly support IndexSignature547if (ts.isIndexSignatureDeclaration(it)) {548return {549name: "indexed",550type: it.type,551questionToken: !!it.questionToken552}553}554if (!ts.isPropertySignature(it)) {555throw new Error(`Expected type literal property to be ts.PropertySignature, not ${asString(it)} got "${it.getText()}"`)556}557return {558name: asString(it.name),559type: it.type!,560questionToken: !!it.questionToken561}562})563
564peer.attributesTypes.push(565{typeName: argumentTypeName, content: this.createParameterType(argumentTypeName, typeLiteralStatements)}566)567// Arkts needs a named type as its argument method, not an anonymous type568// at which producing 'SyntaxError: Invalid Type' error569const peerMethod = peer.methods.find((method) => method.overloadedName == methodName)570if (peerMethod !== undefined) {571peerMethod.method.signature.args = [new Type(argumentTypeName)]572}573return argumentTypeName574}575if (parameters.length > 2) {576const attributeInterfaceStatements = parameters.map(it => ({577name: asString(it.name),578type: it.type!,579questionToken: !!it.questionToken580}))581peer.attributesTypes.push(582{typeName: argumentTypeName, content: this.createParameterType(argumentTypeName, attributeInterfaceStatements)}583)584return argumentTypeName585}586
587return parameters.map(it => mapType(it.type)).join(', ')588}589
590private createParameterType(591name: string,592attributes: { name: string, type: ts.TypeNode, questionToken: boolean }[]593): string {594const attributeDeclarations = attributes595.map(it => `\n ${it.name}${it.questionToken ? "?" : ""}: ${mapType(it.type)}`)596.join('')597return `export interface ${name} {${attributeDeclarations}\n}`598}599
600private fillInterface(peer: PeerClass, node: ts.InterfaceDeclaration) {601peer.originalInterfaceName = identName(node.name)!602const tsMethods = this.extractMethods(node)603const peerMethods = tsMethods604.filter(it => ts.isCallSignatureDeclaration(it))605.map(it => this.processMethodOrCallable(it, peer, identName(node)!))606.filter(isDefined)607PeerMethod.markOverloads(peerMethods)608peer.methods.push(...peerMethods)609}610
611private fillClass(peer: PeerClass, node: ts.ClassDeclaration) {612peer.originalClassName = className(node)613peer.hasGenericType = (node.typeParameters?.length ?? 0) > 0614const parent = singleParentDeclaration(this.declarationTable.typeChecker!, node) as ts.ClassDeclaration615if (parent) {616const parentComponent = this.library.findComponentByDeclaration(parent)!617peer.originalParentName = className(parent)618peer.originalParentFilename = parent.getSourceFile().fileName619peer.parentComponentName = parentComponent.name620}621
622const peerMethods = this.extractMethods(node)623.map(it => this.processMethodOrCallable(it, peer))624.filter(isDefined)625PeerMethod.markOverloads(peerMethods)626peer.methods.push(...peerMethods)627
628this.createComponentAttributesDeclaration(node, peer)629}630
631public generatePeer(component: ComponentDeclaration): void {632const sourceFile = component.attributesDeclarations.parent633if (!ts.isSourceFile(sourceFile))634throw new Error("Expected parent of attributes to be a SourceFile")635const file = this.library.findFileByOriginalFilename(sourceFile.fileName)636if (!file)637throw new Error("Not found a file corresponding to attributes class")638const peer = new PeerClass(file, component.name, sourceFile.fileName, this.declarationTable)639if (component.interfaceDeclaration)640this.fillInterface(peer, component.interfaceDeclaration)641this.fillClass(peer, component.attributesDeclarations)642file.peers.set(component.name, peer)643}644}
645
646export class PeerProcessor {647private readonly typeDependenciesCollector: TypeDependenciesCollector648private readonly declDependenciesCollector: DeclarationDependenciesCollector649private readonly serializeDepsCollector: DeclarationDependenciesCollector650
651constructor(652private readonly library: PeerLibrary,653) {654this.typeDependenciesCollector = new ImportsAggregateCollector(this.library, false)655this.declDependenciesCollector = new FilteredDeclarationCollector(this.library, this.typeDependenciesCollector)656this.serializeDepsCollector = new FilteredDeclarationCollector(657this.library, new ImportsAggregateCollector(this.library, true))658}659private get declarationTable(): DeclarationTable {660return this.library.declarationTable661}662
663private processBuilder(target: ts.InterfaceDeclaration | ts.ClassDeclaration) {664let name = nameOrNull(target.name)!665if (this.library.builderClasses.has(name)) {666return667}668
669if (isCustomBuilderClass(name)) {670return671}672
673const builderClass = toBuilderClass(name, target, this.declarationTable.typeChecker!)674this.library.builderClasses.set(name, builderClass)675}676
677private processMaterialized(target: ts.InterfaceDeclaration | ts.ClassDeclaration) {678let name = nameOrNull(target.name)!679if (this.library.materializedClasses.has(name)) {680return681}682
683const isClass = ts.isClassDeclaration(target)684const isInterface = ts.isInterfaceDeclaration(target)685
686const superClassType = target.heritageClauses687?.filter(it => it.token == ts.SyntaxKind.ExtendsKeyword)[0]?.types[0]688
689
690const superClass = superClassType ?691new SuperElement(692identName(superClassType.expression)!,693superClassType.typeArguments?.filter(ts.isTypeReferenceNode).map(it => identName(it.typeName)!))694: undefined695
696const importFeatures = this.serializeDepsCollector.convert(target)697.filter(it => this.isSourceDecl(it))698.filter(it => PeerGeneratorConfig.needInterfaces || checkTSDeclarationMaterialized(it) || isSyntheticDeclaration(it))699.map(it => convertDeclToFeature(this.library, it))700const generics = target.typeParameters?.map(it => it.getText())701
702let constructor = isClass ? target.members.find(ts.isConstructorDeclaration) : undefined703let mConstructor = this.makeMaterializedMethod(name, constructor)704const finalizerReturnType = {isVoid: false, nativeType: () => PrimitiveType.NativePointer.getText(), macroSuffixPart: () => ""}705let mFinalizer = new MaterializedMethod(name, [], [], finalizerReturnType, false,706new Method("getFinalizer", new NamedMethodSignature(Type.Pointer, [], [], []), [MethodModifier.STATIC]))707let mFields = isClass708? target.members709.filter(ts.isPropertyDeclaration)710.map(it => this.makeMaterializedField(name, it))711: isInterface712? target.members713.filter(ts.isPropertySignature)714.map(it => this.makeMaterializedField(name, it))715: []716
717let mMethods = isClass718? target.members719.filter(ts.isMethodDeclaration)720.map(method => this.makeMaterializedMethod(name, method))721: isInterface722? target.members723.filter(ts.isMethodSignature)724.map(method => this.makeMaterializedMethod(name, method))725: []726this.library.materializedClasses.set(name,727new MaterializedClass(name, isInterface, superClass, generics, mFields, mConstructor, mFinalizer, importFeatures, mMethods))728}729
730private makeMaterializedField(className: string, property: ts.PropertyDeclaration | ts.PropertySignature): MaterializedField {731const name = identName(property.name)!732this.declarationTable.setCurrentContext(`Materialized_${className}_${name}`)733const declarationTarget = this.declarationTable.toTarget(property.type!)734const argConvertor = this.declarationTable.typeConvertor(name, property.type!)735const retConvertor = generateRetConvertor(property.type!)736const modifiers = isReadonly(property.modifiers) ? [FieldModifier.READONLY] : []737this.declarationTable.setCurrentContext(undefined)738return new MaterializedField(declarationTarget, argConvertor, retConvertor,739new Field(name, new Type(mapType(property.type)), modifiers))740}741
742private makeMaterializedMethod(parentName: string, method: ts.ConstructorDeclaration | ts.MethodDeclaration | ts.MethodSignature | undefined) {743const methodName = method === undefined || ts.isConstructorDeclaration(method) ? "ctor" : identName(method.name)!744this.declarationTable.setCurrentContext(`Materialized_${parentName}_${methodName}`)745
746const retConvertor = method === undefined || ts.isConstructorDeclaration(method)747? { isVoid: false, isStruct: false, nativeType: () => PrimitiveType.NativePointer.getText(), macroSuffixPart: () => "" }748: generateRetConvertor(method.type)749
750if (method === undefined) {751// interface or class without constructors752const ctor = new Method("ctor", new NamedMethodSignature(Type.Void, [], []), [MethodModifier.STATIC])753this.declarationTable.setCurrentContext(undefined)754return new MaterializedMethod(parentName, [], [], retConvertor, false, ctor)755}756
757const generics = method.typeParameters?.map(it => it.getText())758const declarationTargets = method.parameters.map(param =>759this.declarationTable.toTarget(param.type ??760throwException(`Expected a type for ${asString(param)} in ${asString(method)}`)))761method.parameters.forEach(it => this.declarationTable.requestType(undefined, it.type!, true))762const argConvertors = method.parameters.map(param => generateArgConvertor(this.declarationTable, param))763const signature = generateSignature(method)764const modifiers = ts.isConstructorDeclaration(method) || isStatic(method.modifiers) ? [MethodModifier.STATIC] : []765this.declarationTable.setCurrentContext(undefined)766return new MaterializedMethod(parentName, declarationTargets, argConvertors, retConvertor, false,767new Method(methodName, signature, modifiers, generics))768}769
770private collectDepsRecursive(node: ts.Declaration | ts.TypeNode, deps: Set<ts.Declaration>): void {771const currentDeps = ts.isTypeNode(node)772? convertTypeNode(this.typeDependenciesCollector, node)773: convertDeclaration(this.declDependenciesCollector, node)774for (const dep of currentDeps) {775if (deps.has(dep)) continue776if (!this.isSourceDecl(dep)) continue777deps.add(dep)778this.collectDepsRecursive(dep, deps)779}780}781
782private processEnum(node: ts.EnumDeclaration) {783const file = this.getDeclSourceFile(node)784let name = node.name.getText()785let comment = getComment(file, node)786let enumEntity = new EnumEntity(name, comment)787node.forEachChild(child => {788if (ts.isEnumMember(child)) {789let name = child.name.getText()790let comment = getComment(file, child)791enumEntity.pushMember(name, comment, child.initializer?.getText())792}793})794this.library.findFileByOriginalFilename(file.fileName)!.pushEnum(enumEntity)795}796
797private isSourceDecl(node: ts.Declaration): boolean {798if (isSyntheticDeclaration(node))799return true800if (ts.isModuleBlock(node.parent))801return this.isSourceDecl(node.parent.parent)802if (ts.isTypeParameterDeclaration(node))803return false804if (!ts.isSourceFile(node.parent))805throw 'Expected declaration to be at file root'806return !node.parent.fileName.endsWith('stdlib.d.ts')807}808
809private getDeclSourceFile(node: ts.Declaration): ts.SourceFile {810if (ts.isModuleBlock(node.parent))811return this.getDeclSourceFile(node.parent.parent)812if (!ts.isSourceFile(node.parent))813throw 'Expected declaration to be at file root'814return node.parent815}816
817private generateActualComponents(): ComponentDeclaration[] {818const components = this.library.componentsDeclarations819if (!this.library.componentsToGenerate.size)820return components821const entryComponents = components.filter(it => this.library.shouldGenerateComponent(it.name))822return components.filter(component => {823return entryComponents.includes(component)824// entryComponents.some(entryComponent => isSubclassComponent(this.declarationTable.typeChecker!, entryComponent, component))825})826}827
828private generateDeclarations(): Set<ts.Declaration> {829const deps = new Set(this.generateActualComponents().flatMap(it => {830const decls: ts.Declaration[] = [it.attributesDeclarations]831if (it.interfaceDeclaration)832decls.push(it.interfaceDeclaration)833return decls834}))835const depsCopy = Array.from(deps)836for (const dep of depsCopy) {837this.collectDepsRecursive(dep, deps)838}839for (const dep of Array.from(deps)) {840if (ts.isEnumMember(dep)) {841deps.add(dep.parent)842deps.delete(dep)843}844}845for (const dep of Array.from(deps)) {846if (PeerGeneratorConfig.isConflictedDeclaration(dep)) {847deps.delete(dep)848this.library.conflictedDeclarations.add(dep)849}850}851return deps852}853
854process(): void {855new ComponentsCompleter(this.library).process()856const peerGenerator = new PeersGenerator(this.library)857for (const component of this.library.componentsDeclarations)858peerGenerator.generatePeer(component)859for (const dep of this.generateDeclarations()) {860if (isSyntheticDeclaration(dep))861continue862const file = this.library.findFileByOriginalFilename(this.getDeclSourceFile(dep).fileName)!863const isPeerDecl = this.library.isComponentDeclaration(dep)864
865if (!isPeerDecl && (ts.isClassDeclaration(dep) || ts.isInterfaceDeclaration(dep))) {866if (isBuilderClass(dep)) {867this.processBuilder(dep)868} else if (isMaterialized(dep)) {869this.processMaterialized(dep)870continue871}872}873
874if (ts.isEnumDeclaration(dep)) {875this.processEnum(dep)876continue877}878
879this.declDependenciesCollector.convert(dep).forEach(it => {880if (this.isSourceDecl(it) && (PeerGeneratorConfig.needInterfaces || isSyntheticDeclaration(it))881&& needImportFeature(this.library.declarationTable.language, it))882file.importFeatures.push(convertDeclToFeature(this.library, it))883})884this.serializeDepsCollector.convert(dep).forEach(it => {885if (this.isSourceDecl(it) && PeerGeneratorConfig.needInterfaces886&& needImportFeature(this.library.declarationTable.language, it)) {887file.serializeImportFeatures.push(convertDeclToFeature(this.library, it))888}889})890if (PeerGeneratorConfig.needInterfaces891&& needImportFeature(this.library.declarationTable.language, dep)) {892file.declarations.add(dep)893file.importFeatures.push(convertDeclToFeature(this.library, dep))894}895}896}897}
898
899function needImportFeature(language: Language, decl: ts.Declaration): boolean {900return !(language === Language.ARKTS && !ts.isEnumDeclaration(decl));901}
902