* 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 idl from "@idlizer/core/idl"
import { getSuper, LibraryInterface } from "@idlizer/core"
import { isDefined } from "@idlizer/core"
import { peerGeneratorConfiguration } from "../DefaultConfiguration"
import path from "node:path"
export class IdlComponentDeclaration {
constructor(
public readonly name: string,
public readonly interfaceDeclaration: idl.IDLInterface | undefined,
public readonly attributeDeclaration: idl.IDLInterface,
) {}
}
const collectComponents_cache = new Map<LibraryInterface, IdlComponentDeclaration[]>()
export function collectComponents(library: LibraryInterface): IdlComponentDeclaration[] {
if (collectComponents_cache.has(library))
return collectComponents_cache.get(library)!
const components = new Array<IdlComponentDeclaration>()
const isCollectedComponent = (entry: idl.IDLEntry): boolean => {
return components.some(it => it.attributeDeclaration === entry || it.interfaceDeclaration === entry)
}
for (const file of library.files) {
for (const entry of idl.linearizeNamespaceMembers(file.entries)) {
if (!idl.isInterface(entry) || !idl.hasExtAttribute(entry, idl.IDLExtendedAttributes.Component))
continue
const componentName = entry.name.replace("Attribute", "")
if (peerGeneratorConfiguration().components.ignoreComponents.includes(componentName) || idl.isHandwritten(entry))
continue
if (idl.hasExtAttribute(entry, idl.IDLExtendedAttributes.HandWrittenImplementation))
continue
const fqn = idl.deriveQualifiedNameFrom(`${componentName}Interface`, entry)
const compInterface = library.resolveTypeReference(idl.createReferenceType(fqn))
if (!compInterface || idl.isInterface(compInterface)) {
components.push(new IdlComponentDeclaration(componentName, compInterface, entry))
}
}
}
for (let i = 0; i < components.length; i++) {
const attributes = components[i].attributeDeclaration
const parentDecl = getSuper(attributes, library)
if (!parentDecl) {
continue
}
if (!idl.isInterface(parentDecl)) {
throw new Error("Expected parent to be a class")
}
if (!isCollectedComponent(parentDecl)) {
components.push(
new IdlComponentDeclaration(parentDecl.name, undefined, parentDecl))
}
}
for (let i = 0; i < components.length; i++) {
for (let j = i + 1; j < components.length; j++) {
if (isSubclassComponent(library, components[i], components[j])) {
components.splice(i, 0, ...components.splice(j, 1))
i--
break
}
}
}
collectComponents_cache.set(library, components)
return components
}
export function isComponentDeclaration(library: LibraryInterface, decl: idl.IDLEntry): boolean {
return collectComponents(library).some(it => it.interfaceDeclaration === decl || it.attributeDeclaration === decl)
}
export function findComponentByDeclaration(library: LibraryInterface, iface: idl.IDLInterface): IdlComponentDeclaration | undefined {
return collectComponents(library).find(it =>
it.interfaceDeclaration === iface || it.attributeDeclaration === iface)
}
export function findComponentByName(library: LibraryInterface, name: string): IdlComponentDeclaration | undefined {
return collectComponents(library).find(it => it.name === name)
}
export function findComponentByType(library: LibraryInterface, type: idl.IDLType): IdlComponentDeclaration | undefined {
return collectComponents(library).find(it =>
idl.forceAsNamedNode(type).name === it.interfaceDeclaration?.name ||
idl.forceAsNamedNode(type).name === it.attributeDeclaration.name)
}
function isSubclassComponent(library: LibraryInterface, a: IdlComponentDeclaration, b: IdlComponentDeclaration) {
return isSubclass(library, a.attributeDeclaration, b.attributeDeclaration)
}
function isSubclass(library: LibraryInterface, component: idl.IDLInterface, maybeParent: idl.IDLInterface): boolean {
const parentDecl = getSuper(component, library)
return isDefined(parentDecl) && (
parentDecl.name === maybeParent.name ||
idl.isInterface(parentDecl) && isSubclass(library, parentDecl, maybeParent))
}