* Copyright (c) 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 {
ConfigTypeInfer,
D,
parseIDLFile,
} from "@idlizer/core";
import * as idl from '@idlizer/core/idl'
import ts from "typescript"
import * as path from "node:path"
import { identName } from "./util.js"
const T = {
stringArray: () => D.array(D.string())
}
export const IDLVisitorConfigurationSchema = D.object({
DeletedDeclarations: T.stringArray(),
DeletedMethods: D.map(D.string(), T.stringArray()),
StubbedDeclarations: T.stringArray(),
NameReplacements: D.map(D.string(), D.array(D.tuple(D.string(), D.string()))),
ForceDefaultExport: D.default(D.map(D.string(), D.string()), new Map()),
TypeReplacementsFilePath: D.string(),
})
export type IDLVisitorConfigurationSchemaType = ConfigTypeInfer<typeof IDLVisitorConfigurationSchema>
export type IDLVisitorConfiguration = IDLVisitorConfigurationSchemaType & IDLVisitorConfigurationHelpers
export interface IDLVisitorConfigurationHelpers {
isDeletedDeclaration(name: string): boolean
isStubbedDeclaration(name: string): boolean
getReplacedDeclaration(name: string): idl.IDLEntry | undefined
checkPropertyTypeReplacement(property: ts.PropertyDeclaration | ts.PropertySignature): [idl.IDLType?, idl.IDLEntry?]
checkParameterTypeReplacement(parameter: ts.ParameterDeclaration): [idl.IDLType?, idl.IDLEntry?]
checkTypedefReplacement(typedef: ts.TypeAliasDeclaration): [idl.IDLType?, idl.IDLEntry?]
checkNameReplacement(name: string, file: ts.SourceFile): string
parsePredefinedIDLFiles(pathBase: string): void
TypeReplacementsFile: idl.IDLFile,
ReplacedDeclarations: Map<string, idl.IDLEntry>
checkMethodSignatureReplacement(methods: (ts.MethodDeclaration | ts.MethodSignature)[]): [idl.IDLMethod[]?, idl.IDLEntry[]?]
}
export function groupOverloadsTS(methods: (ts.MethodDeclaration | ts.MethodSignature)[]): (ts.MethodDeclaration | ts.MethodSignature)[][] {
const groups = new Map<string, (ts.MethodDeclaration | ts.MethodSignature)[]>()
for (const method of methods) {
const methodName = identName(method.name)!
if (!groups.has(methodName)) {
groups.set(methodName, [])
}
const bucket = groups.get(methodName)
bucket?.push(method)
}
return Array.from(groups.values())
}
export function expandIDLVisitorConfig(data:IDLVisitorConfigurationSchemaType): IDLVisitorConfiguration {
return {
...data,
isDeletedDeclaration(name: string) {
return this.DeletedDeclarations.includes(name)
},
isStubbedDeclaration(name: string) {
return this.StubbedDeclarations.includes(name)
},
getReplacedDeclaration(name: string) {
return this.ReplacedDeclarations.get(name)
},
checkPropertyTypeReplacement(property: ts.PropertyDeclaration | ts.PropertySignature): [idl.IDLType?, idl.IDLEntry?] {
const parent = property.parent
if (!ts.isClassDeclaration(parent) && !ts.isInterfaceDeclaration(parent)) return []
const className = identName(parent.name)!
const propertyName = identName(property.name)!
const entries: idl.IDLInterface[] = this.TypeReplacementsFile.entries.filter((it: idl.IDLEntry) => idl.isInterface(it)) as idl.IDLInterface[]
const result = entries.find(it => it.name === className)?.properties.find((it: idl.IDLProperty) => it.name == propertyName)
if (result) {
let syntheticEntry: idl.IDLEntry | undefined
if (idl.isReferenceType(result.type)) {
syntheticEntry = findSyntheticDeclaration(this.TypeReplacementsFile, result.type.name)
}
console.log(`Replaced type for ${className}.${propertyName}`)
return [result.type, syntheticEntry]
}
return []
},
checkParameterTypeReplacement(parameter: ts.ParameterDeclaration): [idl.IDLType?, idl.IDLEntry?] {
const method = parameter.parent
if (!ts.isClassDeclaration(method.parent) && !ts.isInterfaceDeclaration(method.parent)) return []
const parameterName = identName(parameter.name)!
const methodName = identName(method.name)!
const classOrInterfaceName = identName(method.parent.name)!
const entries: idl.IDLInterface[] = this.TypeReplacementsFile.entries.filter((it: idl.IDLEntry) => idl.isInterface(it)) as idl.IDLInterface[]
const result = entries.find(it => it.name === classOrInterfaceName)?.methods.find((it: idl.IDLMethod) => it.name == methodName)?.parameters.find((it: idl.IDLParameter) => it.name == parameterName)
if (result) {
let syntheticEntry: idl.IDLEntry | undefined
if (idl.isReferenceType(result.type)) {
syntheticEntry = findSyntheticDeclaration(this.TypeReplacementsFile, result.type.name)
}
console.log(`Replaced type for ${classOrInterfaceName}.${methodName}(...${parameterName}...)`)
return [idl.maybeOptional(result.type, result.isOptional), syntheticEntry]
}
return []
},
checkMethodSignatureReplacement(overloadedGroup: (ts.MethodDeclaration | ts.MethodSignature)[]): [idl.IDLMethod[]?, idl.IDLEntry[]?] {
const rootMethod = overloadedGroup[0]
if (!ts.isClassDeclaration(rootMethod.parent) && !ts.isInterfaceDeclaration(rootMethod.parent)) return []
const classOrInterfaceName = identName(rootMethod.parent.name)!
const methodName = identName(rootMethod.name)!
const entries: idl.IDLInterface[] = this.TypeReplacementsFile.entries.filter((it: idl.IDLEntry) => idl.isInterface(it)) as idl.IDLInterface[]
const result = entries.find(it => it.name === classOrInterfaceName)?.methods.filter((it: idl.IDLMethod) => it.name == methodName)
if (!result || !result.length) return []
let syntheticEntries: idl.IDLEntry[] = []
for (let idlMethod of result) {
if (idl.isReferenceType(idlMethod.returnType)) {
const syntheticEntry = findSyntheticDeclaration(this.TypeReplacementsFile, idlMethod.returnType.name)
if (syntheticEntry) syntheticEntries.push(syntheticEntry)
}
idlMethod.parameters.forEach(param => {
if (idl.isReferenceType(param.type)) {
const syntheticEntry = findSyntheticDeclaration(this.TypeReplacementsFile, param.type.name)
if (syntheticEntry) syntheticEntries.push(syntheticEntry)
}
})
}
console.log(`Replaced signature for ${classOrInterfaceName}.${methodName}(...)`)
return [result, syntheticEntries.length ? syntheticEntries : undefined]
},
checkTypedefReplacement(typedef: ts.TypeAliasDeclaration): [idl.IDLType?, idl.IDLEntry?] {
const typename = identName(typedef.name)!
let entries: idl.IDLTypedef[] = this.TypeReplacementsFile.entries.filter(it => idl.isTypedef(it)) as idl.IDLTypedef[]
const result = entries.find(it => it.name == typename)
if (result) {
let syntheticEntry: idl.IDLEntry | undefined
if (idl.isReferenceType(result.type)) {
syntheticEntry = findSyntheticDeclaration(this.TypeReplacementsFile, result.type.name)
}
if (idl.isUnionType(result.type)) {
result.type.name = result.name
}
console.log(`Replaced type for typedef ${typename}`)
return [result.type, syntheticEntry]
}
return []
},
checkNameReplacement(name: string, file: ts.SourceFile): string {
const filename: string = path.basename(file.fileName)
const replacementPair = this.NameReplacements.get(filename)
if (replacementPair) {
if (replacementPair[0][0] === name) {
console.log(`Replaced "${name}" with "${replacementPair[1]}" in ${filename}`)
return replacementPair[0][1]
}
}
return name
},
parsePredefinedIDLFiles(pathBase: string) {
this.TypeReplacementsFile = parseIDLFile(path.resolve(path.join(pathBase, this.TypeReplacementsFilePath)))
},
TypeReplacementsFile: idl.createFile([]),
ReplacedDeclarations: new Map<string, idl.IDLEntry>([
["CustomBuilder", idl.createCallback("CustomBuilder", [], idl.createPrimitiveType('void'))],
]),
}
}
function findSyntheticDeclaration(file: idl.IDLFile, declName: string): idl.IDLEntry | undefined {
return file.entries.filter(it => idl.isSyntheticEntry(it)).find(it => it.name == declName)
}