idlize
145 строк · 6.1 Кб
1/*
2* Copyright (c) 2022-2024 Huawei Device Co., Ltd.
3* Licensed under the Apache License, Version 2.0 (the "License");
4* you may not use this file except in compliance with the License.
5* You may obtain a copy of the License at
6*
7* http://www.apache.org/licenses/LICENSE-2.0
8*
9* Unless required by applicable law or agreed to in writing, software
10* distributed under the License is distributed on an "AS IS" BASIS,
11* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12* See the License for the specific language governing permissions and
13* limitations under the License.
14*/
15
16import * as ts from 'ohos-typescript';17import { getDeclarationsByNode } from "./ApiUtils"18
19
20export class ImportExport {21constructor(22public typechecker: ts.TypeChecker,23public sourceFile: ts.SourceFile,24) {}25
26isRealDeclaration(declaration: ts.Declaration): boolean {27if (ts.isImportSpecifier(declaration) ||28ts.isExportSpecifier(declaration) ||29ts.isImportClause(declaration)) return false30return true31}32
33followDefaultImport(identifier: ts.Identifier, importClause: ts.ImportClause): ts.Declaration | undefined {34const moduleSpecifier = importClause.parent.moduleSpecifier35const importSym = this.typechecker.getSymbolAtLocation(moduleSpecifier)36if (importSym === undefined) {37// console.log(`Could not obtain import symbol for ${ts.idText(identifier)}`)38return undefined39}40const exports = this.typechecker.getExportsOfModule(importSym)41
42return arrayAt(exports?.[0]?.declarations, 0)43}44
45followModuleSpecifier(identifier: ts.Identifier, moduleSpecifier: ts.StringLiteral): ts.Declaration | undefined {46const moduleSym = this.typechecker.getSymbolAtLocation(moduleSpecifier)47if (moduleSym === undefined) {48// TODO: The typechecker doesn't give us the symbol in this case49// So be silent for now.50if (moduleSpecifier.text.startsWith("#")) {51return undefined52}53// console.log(`Could not obtain module symbol for ${ts.idText(identifier)}: ${moduleSpecifier.text} `)54return undefined55}56const exports = this.typechecker.getExportsOfModule(moduleSym)57
58const found = exports.find(sym => ts.symbolName(sym) == ts.idText(identifier))59return arrayAt(found?.declarations, 0)60}61
62followImport(identifier: ts.Identifier, declaration: ts.ImportSpecifier): ts.Declaration | undefined {63if (declaration.propertyName) {64const real = getDeclarationsByNode(this.typechecker, declaration.propertyName)[0]65if (real && this.isRealDeclaration(real)) {66return real67} else {68// console.log(`TODO: unexpected declaration: ${real}`)69return undefined70}71}72
73const moduleSpecifier = declaration.parent.parent.parent.moduleSpecifier74if (!ts.isStringLiteral(moduleSpecifier)) {75// console.log("Expected module specifier literal, got: " + ts.SyntaxKind[moduleSpecifier.kind])76return undefined77}78return this.followModuleSpecifier(identifier, moduleSpecifier)79}80
81followExport(identifier: ts.Identifier, exportDeclaration: ts.ExportSpecifier): ts.Declaration | undefined {82const moduleSpecifier = exportDeclaration.parent.parent.moduleSpecifier83const name = exportDeclaration.propertyName ? exportDeclaration.propertyName : identifier84
85if (!moduleSpecifier) {86if (!exportDeclaration.propertyName) {87// This is88// class X{}89// export { X }90// In this case X's declaration is the export directive,91// so need a special way to get to the class declaration.92return this.typechecker.getExportSpecifierLocalTargetSymbol(exportDeclaration)?.declarations?.[0]93} else {94// This is an export from its own module.95return this.findRealDeclaration(exportDeclaration.propertyName)96}97}98if (!ts.isStringLiteral(moduleSpecifier)) {99console.log("Expected module specifier literal, got: " + ts.SyntaxKind[moduleSpecifier.kind])100return undefined101}102
103return this.followModuleSpecifier(name, moduleSpecifier)104}105
106findRealDeclaration(identifier: ts.Identifier): ts.NamedDeclaration | undefined {107const declarations = getDeclarationsByNode(this.typechecker, identifier)108
109if (declarations.length > 1) {110// TODO: shall we support overloads?111// TODO: shall we allow a function and a namespace?112// return error(`TODO: Multiple declarations for ${ts.idText(identifier)} not supported`)113
114return undefined115}116
117let declaration: ts.Declaration | undefined = declarations[0]118while (declaration !== undefined) {119if (this.isRealDeclaration(declaration)) {120return declaration121} else if (ts.isImportSpecifier(declaration)) {122declaration = this.followImport(identifier, declaration)123} else if (ts.isExportSpecifier(declaration)) {124declaration = this.followExport(identifier, declaration)125} else if (ts.isImportClause(declaration)) {126declaration = this.followDefaultImport(identifier, declaration)127} else {128console.log(`Ets plugin non-fatal: Expected a real declaration for ${ts.idText(identifier)}, but got ${declaration ? ts.SyntaxKind[declaration.kind] : undefined}`)129return undefined130}131}132
133return undefined134}135
136findRealMethodDeclaration(member: ts.MemberName): ts.Node | undefined {137if (!ts.isIdentifier(member)) return undefined138const declarations = getDeclarationsByNode(this.typechecker, member)139return declarations[0]140}141}
142
143function arrayAt<T>(array: T[] | undefined, index: number): T | undefined {144return array ? array[index >= 0 ? index : array.length + index] : undefined145}