Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[api-extractor] add basic support for import * as module from './local-module' #1796

Merged
merged 39 commits into from
Jun 30, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
efdde26
added support for 'import * as module from ./local-module'
mckn Mar 25, 2020
1a94da5
added change description.
mckn Mar 25, 2020
79b397e
fixed so we return a proper error messae.
mckn Mar 25, 2020
6e8b0e2
fixed build errors.
mckn Mar 26, 2020
9c3d85a
added missing annotation.
mckn Mar 26, 2020
ec5757a
fixed feedback on pr.
mckn Mar 27, 2020
a4ea1c1
added tests.
mckn Mar 30, 2020
90032e9
merged master into branch.
mckn Apr 2, 2020
64e2c66
fixed version missmatch.
mckn Apr 2, 2020
c88df79
Convert "api-extractor-test-no-report" to be a scenario test
octogonz Apr 7, 2020
dc9bb83
Convert test functions to be stereotypical functions instead of lambd…
octogonz Apr 7, 2020
24ddf89
Merge remote-tracking branch 'remotes/origin/master' into octogonz/ex…
octogonz Apr 16, 2020
0b81dfb
Adjust changelog
octogonz Apr 16, 2020
3651886
Improve test to show how top-level name conflicts are handled
octogonz Apr 17, 2020
de80229
Show how aliases are handled
octogonz Apr 17, 2020
dd449f4
Refactor AstEntity into a proper base class
octogonz May 16, 2020
04a5563
Merge remote-tracking branch 'remotes/origin/release/master-before-pr…
octogonz Aug 28, 2020
11db642
Reapply Prettier following the steps in this comment:
octogonz Aug 28, 2020
872bccb
Merge remote-tracking branch 'remotes/origin/master' into export-star…
octogonz Aug 28, 2020
9481016
Fix lint warnings from merge
octogonz Aug 28, 2020
828baad
Some minor cleanups (no semantic changes)
octogonz Aug 28, 2020
4fbadd8
Add class documentation, rename "exportName" to "namespaceName"
octogonz Aug 28, 2020
cfc693c
Rename AstImportAsModule --> AstNamespaceImport because that's what t…
octogonz Aug 28, 2020
2e75e83
Rename AstImportAsModule --> AstNamespaceImport because that's what t…
octogonz Aug 28, 2020
b2fa123
Tune up the code for generating the .d.ts rollup and add a location f…
octogonz Aug 28, 2020
857a27a
Start work on generating an API report for namespace imports
octogonz Aug 28, 2020
cdc1678
Merge branch 'master' into export-star-as-support
mckn Oct 1, 2020
88ce9ca
fixed issue in change log.
mckn Oct 1, 2020
7ff319d
Merge remote-tracking branch 'remotes/origin/master' into export-star…
octogonz Jun 25, 2021
615a387
rush build
octogonz Jun 25, 2021
2e100e6
Update lockfile
octogonz Jun 25, 2021
8cac97b
Include /// directives in API report, since they are relevant to API …
octogonz Jun 26, 2021
66636f4
Introduce CollectorEntity.consumable distinction to ensure that AstNa…
octogonz Jun 27, 2021
ec267b3
Fix an issue where DocCommentEnhancer was skipping AstNamespaceImport…
octogonz Jun 27, 2021
0ba9748
Eliminate trailing comma
octogonz Jun 27, 2021
51acb54
Add a second test case
octogonz Jun 30, 2021
12799aa
Implement the missing ae-forgotten-export analysis for AstNamespaceIm…
octogonz Jun 30, 2021
fcc005a
Update change log
octogonz Jun 30, 2021
73a3498
Improve the error message for not yet supported "export * as ___ from…
octogonz Jun 30, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions apps/api-extractor/src/analyzer/AstImportAsModule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import { AstModule } from './AstModule';

export interface IAstImportAsModuleOptions {
readonly astModule: AstModule;
readonly exportName: string;
}

// TODO [MA]: add documentation
export class AstImportAsModule {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was there a reason to introduce a new class here rather than integrate this capability into AstSymbol?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or the existing AstImport, which has AstImportKind.StarImport?

// TODO [MA]: add documentation
public analyzed: boolean = false;

// TODO [MA]: add documentation
public readonly astModule: AstModule;

/**
* The name of the symbol being imported.
*
* @remarks
*
* The name depends on the type of import:
*
* ```ts
* // For AstImportKind.DefaultImport style, exportName would be "X" in this example:
* import X from "y";
*
* // For AstImportKind.NamedImport style, exportName would be "X" in this example:
* import { X } from "y";
*
* // For AstImportKind.StarImport style, exportName would be "x" in this example:
* import * as x from "y";
*
* // For AstImportKind.EqualsImport style, exportName would be "x" in this example:
* import x = require("y");
* ```
*/
public readonly exportName: string;

public constructor(options: IAstImportAsModuleOptions) {
this.astModule = options.astModule;
this.exportName = options.exportName;
}

public get localName(): string {
return this.exportName;
}
}
5 changes: 5 additions & 0 deletions apps/api-extractor/src/analyzer/AstReferenceResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { AstModule } from './AstModule';
import { AstImport } from './AstImport';
import { Collector } from '../collector/Collector';
import { DeclarationMetadata } from '../collector/DeclarationMetadata';
import { AstImportAsModule } from './AstImportAsModule';

/**
* Used by `AstReferenceResolver` to report a failed resolution.
Expand Down Expand Up @@ -87,6 +88,10 @@ export class AstReferenceResolver {
return new ResolverFailure('Reexported declarations are not supported');
}

if (rootAstEntity instanceof AstImportAsModule) {
return new ResolverFailure('Source file export declarations are not supported');
}

let currentDeclaration: AstDeclaration | ResolverFailure = this._selectDeclaration(rootAstEntity.astDeclarations,
rootMemberReference, rootAstEntity.localName);

Expand Down
108 changes: 72 additions & 36 deletions apps/api-extractor/src/analyzer/AstSymbolTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ import { AstModule, AstModuleExportInfo } from './AstModule';
import { PackageMetadataManager } from './PackageMetadataManager';
import { ExportAnalyzer } from './ExportAnalyzer';
import { AstImport } from './AstImport';
import { AstImportAsModule } from './AstImportAsModule';
import { MessageRouter } from '../collector/MessageRouter';
import { TypeScriptInternals } from './TypeScriptInternals';
import { StringChecks } from './StringChecks';

export type AstEntity = AstSymbol | AstImport;
export type AstEntity = AstSymbol | AstImport | AstImportAsModule;

/**
* Options for `AstSymbolTable._fetchAstSymbol()`
Expand Down Expand Up @@ -137,43 +138,13 @@ export class AstSymbolTable {
* or members. (We do always construct its parents however, since AstDefinition.parent
* is immutable, and needed e.g. to calculate release tag inheritance.)
*/
public analyze(astSymbol: AstSymbol): void {
if (astSymbol.analyzed) {
return;
public analyze(astEntity: AstEntity): void {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've opened AstSymbolTable.analyze to accept AstEntity which includes AstImport, but there are no cases that handle it in this function.

if (astEntity instanceof AstSymbol) {
return this._analyzeAstSymbol(astEntity);
}

if (astSymbol.nominalAnalysis) {
// We don't analyze nominal symbols
astSymbol._notifyAnalyzed();
return;
}

// Start at the root of the tree
const rootAstSymbol: AstSymbol = astSymbol.rootAstSymbol;

// Calculate the full child tree for each definition
for (const astDeclaration of rootAstSymbol.astDeclarations) {
this._analyzeChildTree(astDeclaration.declaration, astDeclaration);
}

rootAstSymbol._notifyAnalyzed();

if (!astSymbol.isExternal) {
// If this symbol is non-external (i.e. it belongs to the working package), then we also analyze any
// referencedAstSymbols that are non-external. For example, this ensures that forgotten exports
// get analyzed.
rootAstSymbol.forEachDeclarationRecursive((astDeclaration: AstDeclaration) => {
for (const referencedAstEntity of astDeclaration.referencedAstEntities) {

// Walk up to the root of the tree, looking for any imports along the way
if (referencedAstEntity instanceof AstSymbol) {
if (!referencedAstEntity.isExternal) {
this.analyze(referencedAstEntity);
}
}

}
});
if (astEntity instanceof AstImportAsModule) {
return this._analyzeAstImportAsModule(astEntity);
}
}

Expand Down Expand Up @@ -292,6 +263,71 @@ export class AstSymbolTable {
return unquotedName;
}


private _analyzeAstImportAsModule(astImportAsModule: AstImportAsModule): void {
if (astImportAsModule.analyzed) {
return;
}

// mark before actual analyzing, to handle module cyclic reexport
astImportAsModule.analyzed = true;

this.fetchAstModuleExportInfo(astImportAsModule.astModule).exportedLocalEntities.forEach(exportedEntity => {
if (exportedEntity instanceof AstImportAsModule) {
this._analyzeAstImportAsModule(exportedEntity);
}

if (exportedEntity instanceof AstSymbol) {
this._analyzeAstSymbol(exportedEntity);
}
});
}

private _analyzeAstSymbol(astSymbol: AstSymbol): void {
if (astSymbol.analyzed) {
return;
}

if (astSymbol.nominalAnalysis) {
// We don't analyze nominal symbols
astSymbol._notifyAnalyzed();
return;
}

// Start at the root of the tree
const rootAstSymbol: AstSymbol = astSymbol.rootAstSymbol;

// Calculate the full child tree for each definition
for (const astDeclaration of rootAstSymbol.astDeclarations) {
this._analyzeChildTree(astDeclaration.declaration, astDeclaration);
}

rootAstSymbol._notifyAnalyzed();

if (!astSymbol.isExternal) {
// If this symbol is non-external (i.e. it belongs to the working package), then we also analyze any
// referencedAstSymbols that are non-external. For example, this ensures that forgotten exports
// get analyzed.
rootAstSymbol.forEachDeclarationRecursive((astDeclaration: AstDeclaration) => {
for (const referencedAstEntity of astDeclaration.referencedAstEntities) {

// Walk up to the root of the tree, looking for any imports along the way
if (referencedAstEntity instanceof AstSymbol) {
if (!referencedAstEntity.isExternal) {
this._analyzeAstSymbol(referencedAstEntity);
}
}

if (referencedAstEntity instanceof AstImportAsModule) {
if (!referencedAstEntity.astModule.isExternal) {
this._analyzeAstImportAsModule(referencedAstEntity)
}
}
}
});
}
}

/**
* Used by analyze to recursively analyze the entire child tree.
*/
Expand Down
22 changes: 17 additions & 5 deletions apps/api-extractor/src/analyzer/ExportAnalyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { AstModule, AstModuleExportInfo } from './AstModule';
import { TypeScriptInternals } from './TypeScriptInternals';
import { TypeScriptMessageFormatter } from './TypeScriptMessageFormatter';
import { IFetchAstSymbolOptions, AstEntity } from './AstSymbolTable';
import { AstImportAsModule } from './AstImportAsModule';

/**
* Exposes the minimal APIs from AstSymbolTable that are needed by ExportAnalyzer.
Expand All @@ -21,7 +22,7 @@ import { IFetchAstSymbolOptions, AstEntity } from './AstSymbolTable';
export interface IAstSymbolTable {
fetchAstSymbol(options: IFetchAstSymbolOptions): AstSymbol | undefined;

analyze(astSymbol: AstSymbol): void;
analyze(astEntity: AstEntity): void;
}

/**
Expand Down Expand Up @@ -73,6 +74,7 @@ export class ExportAnalyzer {
private readonly _importableAmbientSourceFiles: Set<ts.SourceFile> = new Set<ts.SourceFile>();

private readonly _astImportsByKey: Map<string, AstImport> = new Map<string, AstImport>();
private readonly _astImportAsModuleByModule: Map<AstModule, AstImportAsModule> = new Map<AstModule, AstImportAsModule>();

public constructor(program: ts.Program, typeChecker: ts.TypeChecker, bundledPackageNames: Set<string>,
astSymbolTable: IAstSymbolTable) {
Expand Down Expand Up @@ -308,6 +310,10 @@ export class ExportAnalyzer {
this._astSymbolTable.analyze(astEntity);
}

if (astEntity instanceof AstImportAsModule && !astEntity.astModule.isExternal) {
this._astSymbolTable.analyze(astEntity);
}

astModuleExportInfo.exportedLocalEntities.set(exportSymbol.name, astEntity);
}
}
Expand Down Expand Up @@ -447,10 +453,16 @@ export class ExportAnalyzer {
// SemicolonToken: pre=[;]

if (externalModulePath === undefined) {
// The implementation here only works when importing from an external module.
// The full solution is tracked by: https://github.com/microsoft/rushstack/issues/1029
throw new Error('"import * as ___ from ___;" is not supported yet for local files.'
+ '\nFailure in: ' + importDeclaration.getSourceFile().fileName);
const astModule: AstModule = this._fetchSpecifierAstModule(importDeclaration, declarationSymbol);
let importAsModule: AstImportAsModule | undefined = this._astImportAsModuleByModule.get(astModule);
if (!importAsModule) {
importAsModule = new AstImportAsModule({
exportName: declarationSymbol.name,
astModule: astModule
});
this._astImportAsModuleByModule.set(astModule, importAsModule);
}
mckn marked this conversation as resolved.
Show resolved Hide resolved
return importAsModule;
}

// Here importSymbol=undefined because {@inheritDoc} and such are not going to work correctly for
Expand Down
35 changes: 28 additions & 7 deletions apps/api-extractor/src/collector/Collector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { TypeScriptInternals } from '../analyzer/TypeScriptInternals';
import { MessageRouter } from './MessageRouter';
import { AstReferenceResolver } from '../analyzer/AstReferenceResolver';
import { ExtractorConfig } from '../api/ExtractorConfig';
import { AstImportAsModule } from '../analyzer/AstImportAsModule';

/**
* Options for Collector constructor.
Expand Down Expand Up @@ -294,6 +295,9 @@ export class Collector {
}

public tryFetchMetadataForAstEntity(astEntity: AstEntity): SymbolMetadata | undefined {
if (astEntity instanceof AstImportAsModule) {
return undefined;
}
if (astEntity instanceof AstSymbol) {
return this.fetchSymbolMetadata(astEntity);
}
Expand Down Expand Up @@ -382,10 +386,7 @@ export class Collector {

this._entitiesByAstEntity.set(astEntity, entity);
this._entities.push(entity);

if (astEntity instanceof AstSymbol) {
this._collectReferenceDirectives(astEntity);
}
this._collectReferenceDirectives(astEntity);
}

if (exportedName) {
Expand Down Expand Up @@ -417,6 +418,14 @@ export class Collector {
}
});
}

if (astEntity instanceof AstImportAsModule) {
this.astSymbolTable.fetchAstModuleExportInfo(astEntity.astModule).exportedLocalEntities.forEach((exportedEntity: AstEntity) => {
// Create a CollectorEntity for each top-level export of AstImportInternal entity
this._createCollectorEntity(exportedEntity, undefined);
this._createEntityForIndirectReferences(exportedEntity, alreadySeenAstEntities); // TODO- create entity for module export
});
}
}

/**
Expand Down Expand Up @@ -883,11 +892,23 @@ export class Collector {
return parserContext;
}

private _collectReferenceDirectives(astSymbol: AstSymbol): void {
private _collectReferenceDirectives(astEntity: AstEntity): void {
if (astEntity instanceof AstSymbol) {
const sourceFiles: ts.SourceFile[] = astEntity.astDeclarations.map(astDeclaration =>
astDeclaration.declaration.getSourceFile());
return this._collectReferenceDirectivesFromSourceFiles(sourceFiles);
}

if (astEntity instanceof AstImportAsModule) {
const sourceFiles: ts.SourceFile[] = [astEntity.astModule.sourceFile];
return this._collectReferenceDirectivesFromSourceFiles(sourceFiles);
}
}

private _collectReferenceDirectivesFromSourceFiles(sourceFiles: ts.SourceFile[]): void {
const seenFilenames: Set<string> = new Set<string>();

for (const astDeclaration of astSymbol.astDeclarations) {
const sourceFile: ts.SourceFile = astDeclaration.declaration.getSourceFile();
for (const sourceFile of sourceFiles) {
if (sourceFile && sourceFile.fileName) {
if (!seenFilenames.has(sourceFile.fileName)) {
seenFilenames.add(sourceFile.fileName);
Expand Down
10 changes: 10 additions & 0 deletions apps/api-extractor/src/enhancers/ValidationEnhancer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { SymbolMetadata } from '../collector/SymbolMetadata';
import { CollectorEntity } from '../collector/CollectorEntity';
import { ExtractorMessageId } from '../api/ExtractorMessageId';
import { ReleaseTag } from '@microsoft/api-extractor-model';
import { AstImportAsModule } from '../analyzer/AstImportAsModule';

export class ValidationEnhancer {

Expand All @@ -30,6 +31,10 @@ export class ValidationEnhancer {
ValidationEnhancer._checkForInconsistentReleaseTags(collector, entity.astEntity, symbolMetadata);
}
}

if (entity.astEntity instanceof AstImportAsModule) {
// TODO [MA]: validation for local module import
}
}
}

Expand Down Expand Up @@ -175,6 +180,7 @@ export class ValidationEnhancer {
const rootSymbol: AstSymbol = referencedEntity.rootAstSymbol;

if (!rootSymbol.isExternal) {
// TODO: consider exported by local module import
const collectorEntity: CollectorEntity | undefined = collector.tryGetCollectorEntity(rootSymbol);

if (collectorEntity && collectorEntity.exported) {
Expand Down Expand Up @@ -210,6 +216,10 @@ export class ValidationEnhancer {
}
}
}

if (referencedEntity instanceof AstImportAsModule) {
// TODO [MA]: add validation for local import
}
}
}

Expand Down
Loading