* 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 { basename } from "node:path"
import { capitalize, stringOrNone } from "../util.js"
import { createOptionalType, createContainerType, createReferenceType, createPrimitiveType } from "./builders.js"
import { isNamedNode, isPrimitiveType, QNPattern, getQualifiedName, getFQName, isOptionalType, isContainerType, IDLContainerUtils, isNamespace, isInPackage, isType, isEntry, isTypeParameterType, isInterface, isTypedef, isCallback, isMethod, isCallable, getExtAttribute, hasExtAttribute, getNamespacesPathFor, getPackageName } from "./discriminators.js"
import { IDLNode, IDLNamedNode, IDLKind, IDLExtendedAttributes, IDLType, IDLEntry, IDLReferenceType, IDLContainerType, IDLSignature, IDLParameter, IDLEnum, IDLFile, IDLPrimitiveType } from "./node.js"
import { forEachChild, forEachFunction } from "./visitors.js"
import { Language } from "../Language.js"
export function entityToType(entity: IDLNode): IDLType {
if (isType(entity)) {
return entity
} else if (isEntry(entity)) {
return createReferenceType(entity)
} else {
throw new Error(`Expected to have IDLType or IDLEntry, got ${entity}`)
}
}
export function forceAsNamedNode(type: IDLNode): IDLNamedNode {
if (!isNamedNode(type)) {
throw new Error(`Expected to be an IDLNamedNode, but got '${IDLKind[type.kind]}'`)
}
return type
}
export function isEqualByQualifedName(a?: IDLNamedNode, b?: IDLNamedNode, pattern: QNPattern = "package.namespace.name"): boolean {
if (a === b)
return true
if (!a || !b)
return false
if (a.kind !== b.kind || a.name !== b.name)
return false
return getQualifiedName(a, pattern) === getQualifiedName(b, pattern)
}
export function getVerbatimDts(node: IDLEntry): stringOrNone {
let value = getExtAttribute(node, IDLExtendedAttributes.VerbatimDts)
return value ? value.substring(1, value.length - 1) : undefined
}
* @returns tuple of qualifier name and real type name
*/
export function decomposeQualifiedName(type: IDLReferenceType): [string | undefined, string] {
const typeName = type.name
const lastDot = typeName.lastIndexOf(".")
if (lastDot >= 0) {
const qualifier = typeName.slice(0, lastDot)
const realTypeName = typeName.slice(lastDot + 1)
return [qualifier, realTypeName]
}
return [undefined, typeName]
}
export function qualifiedNameStartsWith(node: IDLNode | string[], template: string[]): boolean {
const name = Array.isArray(node) ? node : getFQName(node).split(".")
if (name.length < template.length)
return false
for (let i = 0; i < template.length; i++) {
if (name[i] != template[i])
return false
}
return true
}
export function maybeUnwrapOptionalType(type: IDLType): IDLType {
if (isOptionalType(type)) {
return type.type
}
return type
}
export function maybeOptional(type: IDLType, optional = false): IDLType {
if (optional) {
return createOptionalType(type)
}
return type
}
export function asPromise(type?: IDLType): IDLContainerType | undefined {
if (!type) return undefined
if (!isContainerType(type)) return undefined
const container = type as IDLContainerType
if (!IDLContainerUtils.isPromise(container)) return undefined
return container
}
export function transformMethodsAsync2ReturnPromise(entry: IDLEntry) {
forEachFunction(entry, function_ => {
if (function_.isAsync) {
function_.isAsync = false
if (!asPromise(function_.returnType))
function_.returnType = createContainerType("Promise", [function_.returnType ?? createPrimitiveType('void')])
}
})
}
export function transformMethodsReturnPromise2Async(entry: IDLEntry) {
forEachFunction(entry, function_ => {
const promise = asPromise(function_.returnType)
if (promise) {
function_.returnType = promise.elementType[0]
function_.isAsync = true
}
})
}
export interface SignatureTag { index: number, name: string, value: string }
export function fetchSignatureTags(node: IDLSignature): SignatureTag[] {
if (!node.extendedAttributes)
return []
return node.extendedAttributes
.filter((ea) => ea.name === IDLExtendedAttributes.DtsTag)
.map((ea): SignatureTag => {
if (!ea.value)
throw new Error('Empty DtsTag is not allowed')
let indexNameValue = ea.value.split('|')
if (indexNameValue.length === 1) {
return {
index: 0,
name: 'type',
value: indexNameValue[0],
}
}
if (indexNameValue.length !== 3)
throw new Error(`Malformed DtsTag: "${ea.value}"`)
return {
index: Number(indexNameValue[0]),
name: indexNameValue[1],
value: indexNameValue[2],
}
})
.sort((a, b) => a.index - b.index)
}
export function mixMethodParametersAndTags(node: IDLSignature): (IDLParameter | SignatureTag)[] {
let mix: (IDLParameter | SignatureTag)[] = node.parameters.slice(0)
for (const tag of fetchSignatureTags(node))
mix.splice(tag.index, 0, tag)
return mix
}
export function isHandwritten(decl: IDLEntry): boolean {
return hasExtAttribute(decl, IDLExtendedAttributes.HandWrittenImplementation)
}
export function isNativeOnly(decl: IDLEntry): boolean {
return hasExtAttribute(decl, IDLExtendedAttributes.NativeOnly)
}
export function isStringEnum(decl: IDLEnum): boolean {
return decl.elements.some(e => isPrimitiveType(e.type, 'String'))
}
export function linearizeNamespaceMembers(entries: IDLEntry[]) {
const linearized: IDLEntry[] = []
for (const entry of entries) {
linearized.push(entry)
if (isNamespace(entry))
linearized.push(...linearizeNamespaceMembers(entry.members))
}
return linearized
}
export function extremumOfOrdinals(enumEntry: IDLEnum): { low: number, high: number } {
if (enumEntry.elements.length == 0) return { low: 0, high: 0 }
let low: number = Number.POSITIVE_INFINITY
let high: number = Number.NEGATIVE_INFINITY
enumEntry.elements.forEach((member, index) => {
let value = index
if ((typeof member.initializer === 'number') && !isStringEnum(enumEntry)) {
value = member.initializer
}
if (low > value) low = value
if (high < value) high = value
})
return { low, high }
}
export function enumBinaryRepresentation(enumEntry: IDLEnum, compact: boolean = false): IDLPrimitiveType {
const { low, high } = extremumOfOrdinals(enumEntry)
if (compact) {
if (0 <= low && high <= 255) return createPrimitiveType('u8')
if (-128 <= low && high <= 127) return createPrimitiveType('i8')
}
if (low < -0x80000000 || high > 0x7FFFFFFF) return createPrimitiveType('i64')
return createPrimitiveType('i32')
}
export const PACKAGE_IDLIZE_INTERNAL = "idlize.internal"
export function isInIdlize(entry: IDLEntry | IDLFile): boolean {
return isInPackage(entry, "idlize")
}
export function isInIdlizeInterop(entry: IDLEntry | IDLFile): boolean {
return isInPackage(entry, `${PACKAGE_IDLIZE_INTERNAL}.interop`)
}
export function isInIdlizeInternal(entry: IDLEntry | IDLFile): boolean {
return isInPackage(entry, PACKAGE_IDLIZE_INTERNAL)
}
export function isInIdlizeStdlib(entry: IDLEntry | IDLFile): boolean {
return isInPackage(entry, "idlize.stdlib")
}
export function isInIdlizeTypescript(entry: IDLEntry | IDLFile): boolean {
return isInPackage(entry, "idlize.typescript")
}
export function hasTypeParameters(entry: IDLEntry): boolean {
let foundTypeParameter = false
forEachChild(entry, n => {
if (isTypeParameterType(n)) {
foundTypeParameter = true
}
})
return foundTypeParameter
}
export function isGeneric(entry: IDLEntry): entry is IDLEntry & { typeParameters?: string[] } {
return isInterface(entry)
|| isTypedef(entry)
|| isCallback(entry)
|| isMethod(entry)
|| isCallable(entry)
}
export function snakeToLowCamelNode(node: IDLEntry): string {
if (!node.fileName) {
throw new Error("Invalid Convert")
}
const classname = basename(node.fileName).replace(".idl", "").replace(".d.ts", "")
return classname
.split('_')
.filter(word => word !== '')
.map((word, index) => {
if (index === 0) {
return word.toLowerCase();
}
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
})
.join('');
}
export function entryToFunctionName(_language: Language, declaration: IDLEntry, prefix: string, postfix: string) {
return `${prefix}${getQualifiedName(declaration, "package.namespace.name").split('.').map(capitalize).join('')}${postfix}`;
}
export function isInNamespace(node: IDLEntry): boolean {
return getNamespacesPathFor(node).length > 0
}