idlize
140 строк · 5.8 Кб
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 'typescript';
17import {
18arrayAt,
19error,
20getDeclarationsByNode,
21} from "./util"
22
23
24export class ImportExport {
25constructor(
26public typechecker: ts.TypeChecker,
27public sourceFile: ts.SourceFile,
28) {}
29
30isRealDeclaration(declaration: ts.Declaration): boolean {
31if (ts.isImportSpecifier(declaration) ||
32ts.isExportSpecifier(declaration) ||
33ts.isImportClause(declaration)) return false
34return true
35}
36
37followDefaultImport(identifier: ts.Identifier, importClause: ts.ImportClause): ts.Declaration | undefined {
38const moduleSpecifier = importClause.parent.moduleSpecifier
39const importSym = this.typechecker.getSymbolAtLocation(moduleSpecifier)
40if (importSym === undefined) {
41return error(`Could not obtain import symbol for ${ts.idText(identifier)}`)
42}
43const exports = this.typechecker.getExportsOfModule(importSym)
44
45return arrayAt(exports?.[0]?.declarations, 0)
46}
47
48followModuleSpecifier(identifier: ts.Identifier, moduleSpecifier: ts.StringLiteral): ts.Declaration | undefined {
49const moduleSym = this.typechecker.getSymbolAtLocation(moduleSpecifier)
50if (moduleSym === undefined) {
51// TODO: The typechecker doesn't give us the symbol in this case
52// So be silent for now.
53if (moduleSpecifier.text.startsWith("#")) {
54return undefined
55}
56return error(`Could not obtain module symbol for ${ts.idText(identifier)}: ${moduleSpecifier.text} `)
57}
58const exports = this.typechecker.getExportsOfModule(moduleSym)
59
60const found = exports.find(sym => ts.symbolName(sym) == ts.idText(identifier))
61return arrayAt(found?.declarations, 0)
62}
63
64followImport(identifier: ts.Identifier, declaration: ts.ImportSpecifier): ts.Declaration | undefined {
65if (declaration.propertyName) {
66const real = getDeclarationsByNode(this.typechecker, declaration.propertyName)[0]
67if (real && this.isRealDeclaration(real)) {
68return real
69} else {
70return error(`TODO: unexpected declaration: ${real}`)
71}
72}
73
74const moduleSpecifier = declaration.parent.parent.parent.moduleSpecifier
75if (!ts.isStringLiteral(moduleSpecifier)) {
76return error("Expected module specifier literal, got: " + ts.SyntaxKind[moduleSpecifier.kind])
77}
78return this.followModuleSpecifier(identifier, moduleSpecifier)
79}
80
81followExport(identifier: ts.Identifier, exportDeclaration: ts.ExportSpecifier): ts.Declaration | undefined {
82const moduleSpecifier = exportDeclaration.parent.parent.moduleSpecifier
83const name = exportDeclaration.propertyName ? exportDeclaration.propertyName : identifier
84
85if (!moduleSpecifier) {
86if (!exportDeclaration.propertyName) {
87// This is
88// 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)) {
99return error("Expected module specifier literal, got: " + ts.SyntaxKind[moduleSpecifier.kind])
100}
101
102return this.followModuleSpecifier(name, moduleSpecifier)
103}
104
105findRealDeclaration(identifier: ts.Identifier): ts.NamedDeclaration | undefined {
106const declarations = getDeclarationsByNode(this.typechecker, identifier)
107
108if (declarations.length > 1) {
109// TODO: shall we support overloads?
110// TODO: shall we allow a function and a namespace?
111// return error(`TODO: Multiple declarations for ${ts.idText(identifier)} not supported`)
112
113return undefined
114}
115
116let declaration: ts.Declaration | undefined = declarations[0]
117while (declaration !== undefined) {
118if (this.isRealDeclaration(declaration)) {
119return declaration
120} else if (ts.isImportSpecifier(declaration)) {
121declaration = this.followImport(identifier, declaration)
122} else if (ts.isExportSpecifier(declaration)) {
123declaration = this.followExport(identifier, declaration)
124} else if (ts.isImportClause(declaration)) {
125declaration = this.followDefaultImport(identifier, declaration)
126} else {
127console.log(`@memo plugin non-fatal: Expected a real declaration for ${ts.idText(identifier)}, but got ${declaration ? ts.SyntaxKind[declaration.kind] : undefined}`)
128return undefined
129}
130}
131
132return undefined
133}
134
135findRealMethodDeclaration(member: ts.MemberName): ts.Node | undefined {
136if (!ts.isIdentifier(member)) return undefined
137const declarations = getDeclarationsByNode(this.typechecker, member)
138return declarations[0]
139}
140}
141