* 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 { IndentedPrinter } from "../IndentedPrinter.js"
import { stringOrNone } from "../util.js"
import { IDLNullTypeName } from "./builders.js"
import { isInterface, isOptionalType, isPrimitiveType, isContainerType, isReferenceType, isUnionType, isTypeParameterType, hasExtAttribute, isFile, isType, isProperty, isParameter, isCallback, isEnum, isTypedef, isConstructor, isMethod } from "./discriminators.js"
import { IDLKeywords } from "./keywords.js"
import { IDLType, IDLInterface, IDLExtendedAttributes, IDLKind, IDLParameter, IDLConstructor, IDLVariable, IDLConstant, IDLProperty, IDLNode, IDLSignature, IDLTypedef, IDLReferenceType, IDLExtendedAttribute, IDLFunction, IDLMethod, IDLFile, IDLImport, IDLNamespace, IDLCallback, IDLEntry, IDLEnumMember, IDLEnum, IDLPrimitiveType } from "./node.js"
export function escapeIDLKeyword(name: string): string {
return name + (IDLKeywords.has(name) ? "_" : "")
}
export function unescapeKeyword(name: string): string {
if (name.endsWith("_")) {
const unwrapped = name.slice(0, -1)
if (IDLKeywords.has(unwrapped)) return unwrapped
}
return name
}
type PrintTypeOptions = {
[key: string]: any
}
export function printType(type: IDLType | IDLInterface | undefined, options?: PrintTypeOptions): string {
if (!type) throw new Error("Missing type")
if (isInterface(type)) return type.name
if (isOptionalType(type)) {
if (hasExtAttribute(type, IDLExtendedAttributes.UnionOnlyNull))
return `(${printType(type.type)} or ${IDLNullTypeName})`
else if (hasExtAttribute(type, IDLExtendedAttributes.UnionWithNull))
return `(${printType(type.type)} or undefined or ${IDLNullTypeName})`
else
return `(${printType(type.type)} or undefined)`
}
if (isPrimitiveType(type)){
const maybeExtendedAttributes = type.extendedAttributes && type.extendedAttributes.length
? `[${quoteAttributeValues(type.extendedAttributes)}] `
: ''
if (maybeExtendedAttributes.length) {
return `(${maybeExtendedAttributes}${type.name})`
}
return type.name
}
if (isContainerType(type)) {
const maybeExtendedAttributes = type.extendedAttributes && type.extendedAttributes.length
? `[${quoteAttributeValues(type.extendedAttributes)}] `
: ''
const res = `${maybeExtendedAttributes}${type.containerKind}<${type.elementType.map(it => printType(it)).join(", ")}>`
if (maybeExtendedAttributes.length) {
return `(${res})`
}
return res
}
if (isReferenceType(type)) {
const maybeExtendedAttributes = type.extendedAttributes && type.extendedAttributes.length
? `[${quoteAttributeValues(type.extendedAttributes)}] `
: ''
const maybeTypeArguments = type.typeArguments && type.typeArguments.length
? '<' + type.typeArguments.map(t => printType(t)).join(', ') + '>'
: ''
let res = `${maybeExtendedAttributes}${type.name}${maybeTypeArguments}`;
if (maybeExtendedAttributes.length && options?.bracketsAroundReferenceTypeWithExtAttrs)
return `(${res})`;
return res;
}
if (isUnionType(type)) return `(${type.types.map(it => printType(it)).join(" or ")})`
if (isTypeParameterType(type)) return type.name
throw new Error(`Cannot map type: ${IDLKind[type.kind]}`)
}
export function nameWithType(idl: IDLVariable, isVariadic: boolean = false, isOptional: boolean = false): string {
const type = printType(idl.type)
const variadic = isVariadic ? "..." : ""
const optional = isOptional ? "optional " : ""
return `${optional}${type}${variadic} ${escapeIDLKeyword(idl.name!)}`
}
const attributesToQuote = new Set([
IDLExtendedAttributes.Documentation,
IDLExtendedAttributes.DtsName,
IDLExtendedAttributes.DtsTag,
IDLExtendedAttributes.Import,
IDLExtendedAttributes.Interfaces,
IDLExtendedAttributes.TraceKey,
IDLExtendedAttributes.TypeParametersDefaults,
])
export function quoteAttributeValues(attributes?: IDLExtendedAttribute[]): stringOrNone {
return attributes
?.map(it => {
let attr = it.name
if (it.value) {
let value = it.value
if (value.includes('"') && !value.includes("'"))
value = value.replaceAll('"', "'")
value = value.replaceAll('\\', '\\\\').replaceAll('"', '\\"')
attr += `=${attributesToQuote.has(it.name as IDLExtendedAttributes) ? `"${value}"` : it.value}`
}
return attr
})
.join(", ")
}
export class IDLWriter {
constructor(protected printer: IndentedPrinter) { }
print(line: stringOrNone): this {
this.printer.print(line)
return this
}
pushIndent(): this {
this.printer.pushIndent()
return this
}
popIndent(): this {
this.printer.popIndent()
return this
}
getOutput(): string[] {
return this.printer.getOutput()
}
printReturnType(type: IDLType | IDLInterface | undefined): string {
return printType(type, { bracketsAroundReferenceTypeWithExtAttrs: true });
}
printParameters(parameters: IDLParameter[]): string {
return parameters
?.map(it =>
nameWithType(it, it.isVariadic, it.isOptional)
)
?.join(", ") ?? ""
}
printConstructor(idl: IDLConstructor): this {
return this.printExtendedAttributes(idl)
.print(`constructor(${this.printParameters(idl.parameters)});`)
}
printConstant(idl: IDLConstant): this {
return this.printExtendedAttributes(idl)
.print(`const ${nameWithType(idl)}${idl.value ? ` = ${idl.value}` : ``};`)
}
printProperty(idl: IDLProperty): this {
const staticMod = idl.isStatic ? "static " : ""
const readonlyMod = idl.isReadonly ? "readonly " : ""
const optional = idl.isOptional ? "optional " : ""
return this.printExtendedAttributes(idl)
.print(`${staticMod}${readonlyMod}${optional}attribute ${nameWithType(idl)};`)
}
printExtendedAttributes(idl: IDLNode): this {
let typeParameters: string[] | undefined
let typeArguments: IDLType[] | undefined
switch (idl.kind) {
case IDLKind.Interface:
typeParameters = (idl as IDLInterface).typeParameters
break
case IDLKind.Callback:
case IDLKind.Method:
case IDLKind.Callable:
case IDLKind.Constructor:
typeParameters = (idl as IDLSignature).typeParameters
break
case IDLKind.Typedef:
typeParameters = (idl as IDLTypedef).typeParameters
break
case IDLKind.ReferenceType:
typeArguments = (idl as IDLReferenceType).typeArguments
break
}
const attributes: IDLExtendedAttribute[] = Array.from(idl.extendedAttributes ?? [])
if (idl.documentation) {
let docs: IDLExtendedAttribute = {
name: IDLExtendedAttributes.Documentation,
value: idl.documentation
}
attributes.unshift(docs)
}
const names = new Set<string>()
const actualAttributes: IDLExtendedAttribute[] = []
for (const attr of attributes) {
if (names.has(attr.name)) {
continue
}
names.add(attr.name)
actualAttributes.push(attr)
}
if (actualAttributes.length == 0) {
return this
}
const attrSpec = quoteAttributeValues(actualAttributes)
if (attrSpec) this.print(`[${attrSpec}]`)
return this
}
printSpacedTypeParameters(params: string[] | undefined): string {
return params && params.length
? '<' + params.join(', ') + '> '
: ''
}
printFunction(idl: IDLFunction): this {
if (idl.name?.startsWith("__")) {
console.log(`Ignore ${idl.name}`)
return this
}
return this.printExtendedAttributes(idl)
.print(`${idl.isAsync ? "async " : ""}` +
`${this.printReturnType(idl.returnType)} ${idl.name}` +
`(${this.printParameters(idl.parameters)});`)
}
printMethod(idl: IDLMethod): this {
if (idl.name?.startsWith("__")) {
console.log(`Ignore ${idl.name}`)
return this
}
return this.printExtendedAttributes(idl)
.print(`${idl.isStatic ? "static " : ""}${idl.isAsync ? "async " : ""}` +
`${this.printSpacedTypeParameters(idl.typeParameters)}` +
`${this.printReturnType(idl.returnType)} ${idl.name}` +
`(${this.printParameters(idl.parameters)});`)
}
printPackage(idl: IDLFile): this {
const effectiveClause = idl.packageClause.filter(it => !!it)
if (!effectiveClause.length) {
return this
}
return this.print(`package ${effectiveClause.join(".")};`)
}
printImport(idl: IDLImport): this {
const effectiveClause = idl.clause.filter(it => !!it)
return this.print(`import ${effectiveClause.join(".") || "NULL_IMPORT"}${idl.name ? " as " : ""}${idl.name};`)
}
printNamespace(idl: IDLNamespace): this {
this.printExtendedAttributes(idl)
.print(`namespace ${idl.name} {`)
.pushIndent()
idl.members.forEach(member => this.printIDL(member))
return this.popIndent().print("};")
}
printCallback(idl: IDLCallback): this {
return this.printExtendedAttributes(idl)
.print(`callback ${this.printSpacedTypeParameters(idl.typeParameters)}${idl.name} = ` +
`${this.printReturnType(idl.returnType)} (${this.printParameters(idl.parameters)});`)
}
printScoped(idl: IDLEntry): this {
if (idl.kind == IDLKind.Callback) return this.printCallback(idl as IDLCallback)
if (idl.kind === IDLKind.Interface) return this.printInterface(idl as IDLInterface)
throw new Error(`Unexpected scoped: ${idl.kind} ${idl.name}`)
}
printInterfaceInherit(idl: IDLInterface): string {
if (idl.inheritance.length === 0) {
return ""
}
const types = idl.inheritance.map(type => printType(type))
return ": " + types.join(', ')
}
printInterfaceHead(decl: IDLInterface): this {
return this.print('interface ' +
this.printSpacedTypeParameters(decl.typeParameters) +
`${decl.name}${this.printInterfaceInherit(decl)} {`)
}
printInterface(idl: IDLInterface): this {
this.printExtendedAttributes(idl)
.printInterfaceHead(idl)
.pushIndent()
idl.constructors.forEach(it => this.printConstructor(it))
idl.constants.forEach(it => this.printConstant(it))
idl.properties.forEach(it => this.printProperty(it))
idl.methods.forEach(it => this.printMethod(it))
idl.callables.forEach(it => this.printFunction(it))
return this.popIndent().print("};")
}
getInitializerValue(type: IDLPrimitiveType, initializer: number | string, decimalType: number | undefined): string {
if (type.name == 'String') return `"${String(initializer).replaceAll('"', "'")}"`
if (decimalType == undefined) throw new Error(`Expected defined enum initializer decimal type for value: ${initializer}`)
switch (decimalType) {
case 2: return `0b${initializer.toString(2)}`
case 16: return `0x${initializer.toString(16).toUpperCase()}`
default: return `${initializer}`
}
}
printEnumMember(idl: IDLEnumMember): this {
const type = printType(idl.type)
const initializer = idl.initializer === undefined
? ''
: ` = ${this.getInitializerValue(idl.type, idl.initializer, idl.initializerDecimalType)}`
return this.print(idl.documentation)
.printExtendedAttributes(idl)
.print(`${type} ${idl.name}${initializer};`)
}
printEnum(idl: IDLEnum, skipInitializers: boolean): this {
this.print(idl.documentation)
.printExtendedAttributes(idl)
if (skipInitializers) {
this.print(`enum ${idl.name!} {`)
.pushIndent()
idl.elements.forEach(it =>
this.print(`${it.name} ${(it.initializer !== undefined ? " /* " + it.initializer + " */" : "")}`))
return this.popIndent().print("};")
} else {
this.print(`enum ${idl.name!} {`)
.pushIndent()
idl.elements.forEach(it => this.printEnumMember(it))
return this.popIndent().print("};")
}
}
printTypedef(idl: IDLTypedef): this {
return this.print(idl.documentation)
.printExtendedAttributes(idl)
.print(`typedef ${this.printSpacedTypeParameters(idl.typeParameters)}${idl.name} = ` +
`${printType(idl.type)};`)
}
printIDL(idl: IDLNode, options?: Partial<IDLPrintOptions>): this {
if (idl.kind == IDLKind.Interface) return this.printInterface(idl as IDLInterface)
if (idl.kind == IDLKind.Enum) return this.printEnum(idl as IDLEnum, options?.disableEnumInitializers ?? false)
if (idl.kind == IDLKind.Typedef) return this.printTypedef(idl as IDLTypedef)
if (idl.kind == IDLKind.Callback) return this.printCallback(idl as IDLCallback)
if (idl.kind == IDLKind.Import) return this.printImport(idl as IDLImport)
if (idl.kind == IDLKind.Namespace) return this.printNamespace(idl as IDLNamespace)
if (idl.kind == IDLKind.Method) return this.printMethod(idl as IDLMethod)
if (idl.kind == IDLKind.Const) return this.printConstant(idl as IDLConstant)
if (idl.kind == IDLKind.Property) return this.printProperty(idl as IDLProperty)
if (options?.allowUnknownKinds) {
return this.print(`${IDLKind[idl.kind]} ${"name" in idl ? (idl as any).name : ""}`)
} else {
throw new Error(`unexpected kind: ${idl.kind}`)
}
}
}
export interface IDLPrintOptions {
verifyIdl: boolean
disableEnumInitializers: boolean
allowUnknownKinds: boolean
oneLine: boolean
}
export function toIDLString(node: IDLNode, options: Partial<IDLPrintOptions>): string {
const writer = new IDLWriter(new IndentedPrinter())
if (isFile(node)) {
writer.printPackage(node)
node.entries.forEach(it => writer.printIDL(it, options))
} else {
writer.printIDL(node, options)
}
return writer.getOutput().join(options.oneLine ? " " : "\n")
}
export const DebugUtils = {
debugPrintType: (type: IDLType): string => {
const filename = type.fileName ? `, fileName: '${type.fileName}'` : ""
if (isContainerType(type)) {
return `[IDLType, name: '${printType(type)}', kind: '${IDLKind[type.kind]}', elements: [${type.elementType.map(DebugUtils.debugPrintType).join(', ')}]${filename}]`
}
return `[IDLType, name: '${printType(type)}', kind: '${IDLKind[type.kind]}'${filename}]`
},
debugPrintTrace: (node?: IDLNode): string => {
const result = []
while (node) {
if (isType(node))
result.push(DebugUtils.debugPrintType(node))
if (isProperty(node))
result.push('property ' + node.name)
if (isParameter(node))
result.push('parameter ' + node.name)
if (isConstructor(node))
result.push('constructor ' + node.name)
if (isMethod(node))
result.push('method ' + node.name)
if (isInterface(node))
result.push('interface ' + node.name)
if (isCallback(node))
result.push('callback ' + node.name)
if (isEnum(node))
result.push('enum ' + node.name)
if (isTypedef(node))
result.push('typedef ' + node.name)
if (isFile(node))
result.push('file ' + node.fileName)
node = node.parent
}
return result.reduce((acc, it, i) => acc + ' '.repeat(i * 2) + it + '\n', '')
}
}