* Copyright (c) 2024-2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as path from "path"
import { ArgumentModifier, capitalize, getSuper, isDefined, LibraryInterface, Method, MethodModifier, NamedMethodSignature, PeerClass, PeerLibrary, PeerMethod, PeerMethodArg, PeerMethodSignature, warn } from "@idlizer/core";
import * as idl from "@idlizer/core/idl"
import { collectComponents, findComponentByDeclaration, findComponentByType, IdlComponentDeclaration } from "./ComponentsCollector";
import { getMethodModifiers } from "./idl/IdlPeerGeneratorVisitor";
import { peerGeneratorConfiguration } from "../DefaultConfiguration";
const collectPeers_cache = new Map<LibraryInterface, PeerClass[]>()
const componentPeers_cache = new Map<LibraryInterface, Map<IdlComponentDeclaration, PeerClass>>()
export function collectPeers(library: PeerLibrary): PeerClass[] {
if (!collectPeers_cache.has(library))
collectPeers_cache.set(library, collectComponents(library).map(it => generatePeer(library, it)))
return collectPeers_cache.get(library)!
}
export function findPeerByComponentDeclaration(library: PeerLibrary, component: IdlComponentDeclaration): PeerClass | undefined {
return componentPeers_cache.get(library)?.get(component)
}
export function collectOrderedPeers(library: PeerLibrary): PeerClass[] {
return Array.from(collectPeers(library)).sort((a, b) => a.componentName.localeCompare(b.componentName))
}
export function collectPeersForFile(library: PeerLibrary, file: idl.IDLFile): PeerClass[] {
return collectPeers(library).filter(it => it.file === file)
}
export function extractContentParameter(method: idl.IDLMethod | idl.IDLCallable): {
hasContentParameter: boolean,
parameters: idl.IDLParameter[]
} {
if (idl.isCallable(method) && method.parameters.at(-1)?.name === 'content_') {
return {
hasContentParameter: true,
parameters: method.parameters.slice(0, -1)
}
}
return {
hasContentParameter: false,
parameters: method.parameters
}
}
function processMethodOrCallable(library: PeerLibrary, method: idl.IDLMethod | idl.IDLCallable, peer: PeerClass, parentName?: string): PeerMethod | undefined {
if (peerGeneratorConfiguration().components.ignorePeerMethod.includes(method.name!))
return
const isCallSignature = idl.isCallable(method)
const methodName = isCallSignature ? `set${capitalize(peer.componentName)}Options` : method.name
const retType = method.returnType!
const isThisRet = isCallSignature || idl.isNamedNode(retType) && (retType.name === peer.originalClassName || retType.name === "T" || retType === idl.IDLThisType)
const originalParentName = parentName ?? peer.originalClassName!
const { parameters } = extractContentParameter(method)
const signature = new NamedMethodSignature(
(isThisRet ? idl.IDLThisType : retType) ?? method.returnType!,
parameters.map(it => it.type),
parameters.map(it => it.name),
undefined,
parameters.map(it => it.isOptional ? ArgumentModifier.OPTIONAL : undefined)
)
const realRetType = isThisRet ? idl.IDLVoidType : retType
const overloadInfo = PeerMethodSignature.mangleOverloadedName(method)
const newMethodName = isCallSignature
? methodName + overloadInfo.postfix
: `set${capitalize(overloadInfo.alias ?? (methodName + overloadInfo.postfix))}`
if (isCallSignature) {
peer.componentBuilderInfos.push({
uniqueOverloadName: overloadInfo.alias ?? peer.componentName,
peerMethodName: newMethodName,
})
}
return new PeerMethod(
method,
new PeerMethodSignature(
newMethodName,
idl.getFQName(method.parent as idl.IDLInterface).split('.').concat(newMethodName).join('_'),
signature.args.map((it, index) => new PeerMethodArg(signature.argName(index), idl.maybeOptional(it, signature.isArgOptional(index)))),
signature.returnType,
method.parent as idl.IDLInterface,
peerGeneratorConfiguration().forceContext.includes(idl.getFQName(method)) ? [MethodModifier.FORCE_CONTEXT] : undefined
),
originalParentName,
realRetType,
isCallSignature,
isCallSignature
? (library.useComponentNamedOverloads ? newMethodName : methodName)
: overloadInfo.alias ?? methodName,
new Method(methodName!, signature, getMethodModifiers(method))
)
}
function fillInterface(library: PeerLibrary, peer: PeerClass, iface: idl.IDLInterface) {
peer.originalInterfaceName = iface.name
const peerMethods = iface.callables
.map(it => processMethodOrCallable(library, it, peer, iface?.name))
.filter(isDefined)
peer.methods.push(...peerMethods)
}
function processProperty(library: PeerLibrary, prop: idl.IDLProperty, peer: PeerClass, parentName?: string): PeerMethod | undefined {
if (peerGeneratorConfiguration().components.ignorePeerMethod.includes(prop.name))
return
const originalParentName = parentName ?? peer.originalClassName!
const signature = new NamedMethodSignature(idl.IDLThisType, [idl.maybeOptional(prop.type, prop.isOptional)], ["value"])
const overloadInfo = PeerMethodSignature.mangleOverloadedName(prop)
const methodName = `set${capitalize(overloadInfo.alias ?? (prop.name + overloadInfo.postfix))}`
return new PeerMethod(
prop,
new PeerMethodSignature(
methodName,
idl.getFQName(prop.parent as idl.IDLInterface).split('.').concat(methodName).join('_'),
[new PeerMethodArg('value', idl.maybeOptional(prop.type, prop.isOptional))],
idl.IDLVoidType,
prop.parent as idl.IDLInterface,
peerGeneratorConfiguration().forceContext.includes(idl.getFQName(prop)) ? [MethodModifier.FORCE_CONTEXT] : undefined
),
originalParentName,
idl.IDLVoidType,
false,
overloadInfo.alias ?? prop.name,
new Method(prop.name, signature, []))
}
function processOptionAttribute(seenAttributes: Set<string>, property: idl.IDLProperty, peer: PeerClass) {
const propName = property.name
if (seenAttributes.has(propName)) {
warn(`ignore seen property: ${propName}`)
return
}
seenAttributes.add(propName)
peer.attributesFields.push(property)
}
function createComponentAttributesDeclaration(clazz: idl.IDLInterface, peer: PeerClass) {
if (peerGeneratorConfiguration().components.invalidAttributes.includes(peer.componentName)) {
return
}
const seenAttributes = new Set<string>()
clazz.properties.forEach(prop => {
processOptionAttribute(seenAttributes, prop, peer)
})
}
function fillClass(library: PeerLibrary, peer: PeerClass, clazz: idl.IDLInterface) {
peer.originalClassName = clazz.name
const parentDecl = getSuper(clazz, library)
if (parentDecl) {
const parentComponent = findComponentByDeclaration(library, parentDecl)!
peer.originalParentName = parentDecl?.name
peer.originalParentFilename = parentDecl?.fileName
peer.parentComponentName = parentComponent.name
}
const peerMethods = [
...clazz.properties.map(it => processProperty(library, it, peer)),
...clazz.methods.map(it => processMethodOrCallable(library, it, peer)),
].filter(isDefined)
peer.methods.push(...peerMethods)
createComponentAttributesDeclaration(clazz, peer)
}
function fillComponentPeersCache(library: PeerLibrary, component: IdlComponentDeclaration, peer: PeerClass) {
if (!componentPeers_cache.has(library)) {
componentPeers_cache.set(library, new Map<IdlComponentDeclaration, PeerClass>())
}
componentPeers_cache.get(library)!.set(component, peer)
}
function generatePeer(library: PeerLibrary, component: IdlComponentDeclaration): PeerClass {
if (!component.attributeDeclaration.fileName) {
throw new Error("Expected parent of attributes to be a SourceFile, but fileName is undefined")
}
const originalFileName = component.attributeDeclaration.fileName
const baseName = path.basename(originalFileName)
const resolvedPath = path.resolve(originalFileName)
const file = library.findFileByOriginalFilename(baseName) ||
library.findFileByOriginalFilename(resolvedPath)
if (!file) {
console.error("Available files in library:", library.files.map(f => f.fileName))
throw new Error(`Not found a file corresponding to attributes class: ${baseName} (${resolvedPath})`)
}
const peer = new PeerClass(component.attributeDeclaration, file, component.name, baseName)
fillComponentPeersCache(library, component, peer)
if (component.interfaceDeclaration) {
fillInterface(library, peer, component.interfaceDeclaration)
}
fillClass(library, peer, component.attributeDeclaration)
return peer
}