* Copyright (c) 2024 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 '../idl'
import { getPov, resolveNamedNode } from '../resolveNamedNode'
export interface ReferenceResolver {
resolveTypeReference(type: idl.IDLReferenceType, options?: { terminalImports?: boolean, unresolvedOk?: boolean }): idl.IDLEntry | undefined
toDeclaration(type: idl.IDLNode): idl.IDLNode
}
export function createEmptyReferenceResolver(): ReferenceResolver {
return {
resolveTypeReference() {
return undefined
},
toDeclaration(type) {
return type
}
}
}
export function createAlternativeReferenceResolver(mainResolver: ReferenceResolver, alternatives: Map<string, idl.IDLEntry>): ReferenceResolver {
return {
resolveTypeReference(type, options?: { terminalImports?: boolean, unresolvedOk?: boolean }) {
return mainResolver.resolveTypeReference(type, options) ?? alternatives.get(type.name)
},
toDeclaration(type) {
return mainResolver.toDeclaration(type)
},
}
}
export function createAlgotithmicReferenceResolver(files: idl.IDLFile[], useFallback: boolean = false): ReferenceResolver {
const resolveFallback = (type: idl.IDLReferenceType): idl.IDLNode | undefined => {
const target = type.name.split('.')
const resolveds = new Set<idl.IDLNode>()
for (let file of files) {
const result = resolveNamedNode([...file.packageClause, ...target], undefined, files)
if (result && idl.isEntry(result)) {
resolveds.add(result)
}
}
const traverseNamespaces = (entry: idl.IDLEntry) => {
if (entry && idl.isNamespace(entry) && entry.members.length) {
const resolved = resolveNamedNode([...idl.getNamespacesPathFor(entry).map(it => it.name), ...target], undefined, files)
if (resolved) {
console.warn(`WARNING: Name '${type.name}' resolved by fallback from some namespace: '${idl.getNamespacesPathFor(resolved).map(obj => obj.name).join(".")}'`)
resolveds.add(resolved)
}
entry.members.forEach(traverseNamespaces)
}
}
files.forEach(file => file.entries.forEach(traverseNamespaces))
if (resolveds.size > 1)
console.error(`Resolved multiple possible declarations by ${idl.DebugUtils.debugPrintType(type)}`)
return resolveds.size ? resolveds.values().next().value : undefined
}
return {
resolveTypeReference(type, options?: { terminalImports?: boolean, unresolvedOk?: boolean }) {
let result: idl.IDLNode | undefined = resolveNamedNode(type.name.split("."), getPov(type), files) ?? (useFallback ? resolveFallback(type) : undefined)
if (!options?.terminalImports) {
const seen = new Set<idl.IDLNode>
while (result) {
let nextResult: idl.IDLNode | undefined = undefined
if (idl.isImport(result))
nextResult = resolveNamedNode(result.clause, undefined, files)
if (!nextResult)
break;
if (seen.has(nextResult)) {
console.warn(`Cyclic referenceType: ${type.name}, seen: [${[...seen.values()].map(idl.getFQName).join(", ")}]`)
break;
}
seen.add(nextResult)
result = nextResult
}
}
if (result) {
if (!idl.isEntry(result)) {
throw new Error(`Resolved reference ${idl.DebugUtils.debugPrintType(type)} to not entry type`)
}
if (idl.isNamespace(result)) {
throw new Error(`Resolved reference ${idl.DebugUtils.debugPrintType(type)} to namespace`)
}
return result
}
return undefined
},
toDeclaration(type) {
throw new Error("To declaration is not available for default references resolver")
}
}
}
export function createCachedReferenceResolver(files: idl.IDLFile[]): ReferenceResolver {
const cache: Map<string, idl.IDLEntry> = new Map()
for (const file of files) {
idl.forEachChild(file, node => {
if (idl.isEnum(node) || idl.isCallback(node) || idl.isInterface(node) || idl.isTypedef(node)) {
const fqn = idl.getFQName(node)
if (cache.has(fqn))
console.warn(`WARNING: multiple entries with FQN=${fqn} found`)
else
cache.set(fqn, node)
}
})
}
return {
resolveTypeReference(type, options?: { terminalImports?: boolean, unresolvedOk?: boolean }) {
if (!options?.unresolvedOk && !cache.has(type.name))
console.warn(`WARNING: reference ${idl.DebugUtils.debugPrintType(type)} was not found`)
return cache.get(type.name)
},
toDeclaration(type) {
throw new Error("Not implemented")
},
}
}