Skip to content

Commit 3ade23f

Browse files
authored
Merge pull request #1796 from grafana/export-star-as-support
[api-extractor] add basic support for `import * as module from './local-module'`
2 parents fa82aa9 + 73a3498 commit 3ade23f

38 files changed

+1644
-164
lines changed

apps/api-extractor/.vscode/launch.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,13 @@
7474
"name": "scenario",
7575
"program": "${workspaceFolder}/lib/start.js",
7676
"cwd": "${workspaceFolder}/../../build-tests/api-extractor-scenarios",
77-
"args": ["--debug", "run", "--local", "--config", "./temp/configs/api-extractor-typeof.json"],
77+
"args": [
78+
"--debug",
79+
"run",
80+
"--local",
81+
"--config",
82+
"./temp/configs/api-extractor-exportImportStarAs2.json"
83+
],
7884
"sourceMaps": true
7985
}
8086
]

apps/api-extractor/src/analyzer/AstDeclaration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as ts from 'typescript';
55
import { AstSymbol } from './AstSymbol';
66
import { Span } from './Span';
77
import { InternalError } from '@rushstack/node-core-library';
8-
import { AstEntity } from './AstSymbolTable';
8+
import { AstEntity } from './AstEntity';
99

1010
/**
1111
* Constructor options for AstDeclaration
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2+
// See LICENSE in the project root for license information.
3+
4+
/**
5+
* `AstEntity` is the abstract base class for analyzer objects that can become a `CollectorEntity`.
6+
*
7+
* @remarks
8+
*
9+
* The subclasses are:
10+
* ```
11+
* - AstEntity
12+
* - AstSymbol
13+
* - AstSyntheticEntity
14+
* - AstImport
15+
* - AstNamespaceImport
16+
* ```
17+
*/
18+
export abstract class AstEntity {
19+
/**
20+
* The original name of the symbol, as exported from the module (i.e. source file)
21+
* containing the original TypeScript definition. Constructs such as
22+
* `import { X as Y } from` may introduce other names that differ from the local name.
23+
*
24+
* @remarks
25+
* For the most part, `localName` corresponds to `followedSymbol.name`, but there
26+
* are some edge cases. For example, the ts.Symbol.name for `export default class X { }`
27+
* is actually `"default"`, not `"X"`.
28+
*/
29+
public abstract readonly localName: string;
30+
}
31+
32+
/**
33+
* `AstSyntheticEntity` is the abstract base class for analyzer objects whose emitted declarations
34+
* are not text transformations performed by the `Span` helper.
35+
*
36+
* @remarks
37+
* Most of API Extractor's output is produced by using the using the `Span` utility to regurgitate strings from
38+
* the input .d.ts files. If we need to rename an identifier, the `Span` visitor can pick out an interesting
39+
* node and rewrite its string, but otherwise the transformation operates on dumb text and not compiler concepts.
40+
* (Historically we did this because the compiler's emitter was an internal API, but it still has some advantages,
41+
* for example preserving syntaxes generated by an older compiler to avoid incompatibilities.)
42+
*
43+
* This strategy does not work for cases where the output looks very different from the input. Today these
44+
* cases are always kinds of `import` statements, but that may change in the future.
45+
*/
46+
export abstract class AstSyntheticEntity extends AstEntity {}

apps/api-extractor/src/analyzer/AstImport.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import { AstSymbol } from './AstSymbol';
55
import { InternalError } from '@rushstack/node-core-library';
6+
import { AstSyntheticEntity } from './AstEntity';
67

78
/**
89
* Indicates the import kind for an `AstImport`.
@@ -49,7 +50,7 @@ export interface IAstImportOptions {
4950
* For a symbol that was imported from an external package, this tracks the import
5051
* statement that was used to reach it.
5152
*/
52-
export class AstImport {
53+
export class AstImport extends AstSyntheticEntity {
5354
public readonly importKind: AstImportKind;
5455

5556
/**
@@ -110,6 +111,8 @@ export class AstImport {
110111
public readonly key: string;
111112

112113
public constructor(options: IAstImportOptions) {
114+
super();
115+
113116
this.importKind = options.importKind;
114117
this.modulePath = options.modulePath;
115118
this.exportName = options.exportName;
@@ -120,11 +123,9 @@ export class AstImport {
120123
this.key = AstImport.getKey(options);
121124
}
122125

123-
/**
124-
* Allows `AstEntity.localName` to be used as a convenient generalization of `AstSymbol.localName` and
125-
* `AstImport.exportName`.
126-
*/
126+
/** {@inheritdoc} */
127127
public get localName(): string {
128+
// abstract
128129
return this.exportName;
129130
}
130131

apps/api-extractor/src/analyzer/AstModule.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import * as ts from 'typescript';
55

66
import { AstSymbol } from './AstSymbol';
7-
import { AstEntity } from './AstSymbolTable';
7+
import { AstEntity } from './AstEntity';
88

99
/**
1010
* Represents information collected by {@link AstSymbolTable.fetchAstModuleExportInfo}
@@ -16,6 +16,12 @@ export class AstModuleExportInfo {
1616

1717
/**
1818
* Constructor parameters for AstModule
19+
*
20+
* @privateRemarks
21+
* Our naming convention is to use I____Parameters for constructor options and
22+
* I____Options for general function options. However the word "parameters" is
23+
* confusingly similar to the terminology for function parameters modeled by API Extractor,
24+
* so we use I____Options for both cases in this code base.
1925
*/
2026
export interface IAstModuleOptions {
2127
sourceFile: ts.SourceFile;
@@ -25,12 +31,6 @@ export interface IAstModuleOptions {
2531

2632
/**
2733
* An internal data structure that represents a source file that is analyzed by AstSymbolTable.
28-
*
29-
* @privateRemarks
30-
* Our naming convention is to use I____Parameters for constructor options and
31-
* I____Options for general function options. However the word "parameters" is
32-
* confusingly similar to the terminology for function parameters modeled by API Extractor,
33-
* so we use I____Options for both cases in this code base.
3434
*/
3535
export class AstModule {
3636
/**
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2+
// See LICENSE in the project root for license information.
3+
4+
import * as ts from 'typescript';
5+
6+
import { AstModule, AstModuleExportInfo } from './AstModule';
7+
import { AstSyntheticEntity } from './AstEntity';
8+
import { Collector } from '../collector/Collector';
9+
10+
export interface IAstNamespaceImportOptions {
11+
readonly astModule: AstModule;
12+
readonly namespaceName: string;
13+
readonly declaration: ts.Declaration;
14+
}
15+
16+
/**
17+
* `AstNamespaceImport` represents a namespace that is created implicitly by a statement
18+
* such as `import * as example from "./file";`
19+
*
20+
* @remarks
21+
*
22+
* A typical input looks like this:
23+
* ```ts
24+
* // Suppose that example.ts exports two functions f1() and f2().
25+
* import * as example from "./file";
26+
* export { example };
27+
* ```
28+
*
29+
* API Extractor's .d.ts rollup will transform it into an explicit namespace, like this:
30+
* ```ts
31+
* declare f1(): void;
32+
* declare f2(): void;
33+
*
34+
* declare namespace example {
35+
* export {
36+
* f1,
37+
* f2
38+
* }
39+
* }
40+
* ```
41+
*
42+
* The current implementation does not attempt to relocate f1()/f2() to be inside the `namespace`
43+
* because other type signatures may reference them directly (without using the namespace qualifier).
44+
* The `declare namespace example` is a synthetic construct represented by `AstNamespaceImport`.
45+
*/
46+
export class AstNamespaceImport extends AstSyntheticEntity {
47+
/**
48+
* Returns true if the AstSymbolTable.analyze() was called for this object.
49+
* See that function for details.
50+
*/
51+
public analyzed: boolean = false;
52+
53+
/**
54+
* For example, if the original statement was `import * as example from "./file";`
55+
* then `astModule` refers to the `./file.d.ts` file.
56+
*/
57+
public readonly astModule: AstModule;
58+
59+
/**
60+
* For example, if the original statement was `import * as example from "./file";`
61+
* then `namespaceName` would be `example`.
62+
*/
63+
public readonly namespaceName: string;
64+
65+
/**
66+
* The original `ts.SyntaxKind.NamespaceImport` which can be used as a location for error messages.
67+
*/
68+
public readonly declaration: ts.Declaration;
69+
70+
public constructor(options: IAstNamespaceImportOptions) {
71+
super();
72+
this.astModule = options.astModule;
73+
this.namespaceName = options.namespaceName;
74+
this.declaration = options.declaration;
75+
}
76+
77+
/** {@inheritdoc} */
78+
public get localName(): string {
79+
// abstract
80+
return this.namespaceName;
81+
}
82+
83+
public fetchAstModuleExportInfo(collector: Collector): AstModuleExportInfo {
84+
const astModuleExportInfo: AstModuleExportInfo = collector.astSymbolTable.fetchAstModuleExportInfo(
85+
this.astModule
86+
);
87+
return astModuleExportInfo;
88+
}
89+
}

apps/api-extractor/src/analyzer/AstReferenceResolver.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
import * as ts from 'typescript';
55
import * as tsdoc from '@microsoft/tsdoc';
66

7-
import { AstSymbolTable, AstEntity } from './AstSymbolTable';
7+
import { AstSymbolTable } from './AstSymbolTable';
8+
import { AstEntity } from './AstEntity';
89
import { AstDeclaration } from './AstDeclaration';
910
import { WorkingPackage } from '../collector/WorkingPackage';
1011
import { AstModule } from './AstModule';
11-
import { AstImport } from './AstImport';
1212
import { Collector } from '../collector/Collector';
1313
import { DeclarationMetadata } from '../collector/DeclarationMetadata';
14+
import { AstSymbol } from './AstSymbol';
1415

1516
/**
1617
* Used by `AstReferenceResolver` to report a failed resolution.
@@ -90,8 +91,8 @@ export class AstReferenceResolver {
9091
);
9192
}
9293

93-
if (rootAstEntity instanceof AstImport) {
94-
return new ResolverFailure('Reexported declarations are not supported');
94+
if (!(rootAstEntity instanceof AstSymbol)) {
95+
return new ResolverFailure('This type of declaration is not supported yet by the resolver');
9596
}
9697

9798
let currentDeclaration: AstDeclaration | ResolverFailure = this._selectDeclaration(

apps/api-extractor/src/analyzer/AstSymbol.ts

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import * as ts from 'typescript';
55
import { AstDeclaration } from './AstDeclaration';
66
import { InternalError } from '@rushstack/node-core-library';
7+
import { AstEntity } from './AstEntity';
78

89
/**
910
* Constructor options for AstSymbol
@@ -50,18 +51,9 @@ export interface IAstSymbolOptions {
5051
* - g
5152
* ```
5253
*/
53-
export class AstSymbol {
54-
/**
55-
* The original name of the symbol, as exported from the module (i.e. source file)
56-
* containing the original TypeScript definition. Constructs such as
57-
* `import { X as Y } from` may introduce other names that differ from the local name.
58-
*
59-
* @remarks
60-
* For the most part, `localName` corresponds to `followedSymbol.name`, but there
61-
* are some edge cases. For example, the symbol name for `export default class X { }`
62-
* is actually `"default"`, not `"X"`.
63-
*/
64-
public readonly localName: string;
54+
export class AstSymbol extends AstEntity {
55+
/** {@inheritdoc} */
56+
public readonly localName: string; // abstract
6557

6658
/**
6759
* If true, then the `followedSymbol` (i.e. original declaration) of this symbol
@@ -121,6 +113,8 @@ export class AstSymbol {
121113
private _analyzed: boolean = false;
122114

123115
public constructor(options: IAstSymbolOptions) {
116+
super();
117+
124118
this.followedSymbol = options.followedSymbol;
125119
this.localName = options.localName;
126120
this.isExternal = options.isExternal;

0 commit comments

Comments
 (0)