Skip to content

Commit

Permalink
Merge pull request #1985 from microsoft/octogonz/import-type
Browse files Browse the repository at this point in the history
[api-extractor] Add support for "import type" imports
  • Loading branch information
octogonz authored Jul 3, 2020
2 parents adfc73d + d0eef69 commit 743ca2e
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 12 deletions.
15 changes: 15 additions & 0 deletions apps/api-extractor/src/analyzer/AstImport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export interface IAstImportOptions {
readonly importKind: AstImportKind;
readonly modulePath: string;
readonly exportName: string;
readonly isTypeOnly: boolean;
}

/**
Expand Down Expand Up @@ -82,6 +83,17 @@ export class AstImport {
*/
public readonly exportName: string;

/**
* Whether it is a type-only import, for example:
*
* ```ts
* import type { X } from "y";
* ```
*
* This is set to true ONLY if the type-only form is used in *every* reference to this AstImport.
*/
public isTypeOnlyEverywhere: boolean;

/**
* If this import statement refers to an API from an external package that is tracked by API Extractor
* (according to `PackageMetadataManager.isAedocSupportedFor()`), then this property will return the
Expand All @@ -102,6 +114,9 @@ export class AstImport {
this.modulePath = options.modulePath;
this.exportName = options.exportName;

// We start with this assumption, but it may get changed later if non-type-only import is encountered.
this.isTypeOnlyEverywhere = options.isTypeOnly;

this.key = AstImport.getKey(options);
}

Expand Down
31 changes: 25 additions & 6 deletions apps/api-extractor/src/analyzer/ExportAnalyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,8 @@ export class ExportAnalyzer {
return this._fetchAstImport(declarationSymbol, {
importKind: AstImportKind.NamedImport,
modulePath: externalModulePath,
exportName: exportName
exportName: exportName,
isTypeOnly: false
});
}

Expand Down Expand Up @@ -498,7 +499,8 @@ export class ExportAnalyzer {
return this._fetchAstImport(undefined, {
importKind: AstImportKind.StarImport,
exportName: declarationSymbol.name,
modulePath: externalModulePath
modulePath: externalModulePath,
isTypeOnly: ExportAnalyzer._getIsTypeOnly(importDeclaration)
});
}

Expand Down Expand Up @@ -530,7 +532,8 @@ export class ExportAnalyzer {
return this._fetchAstImport(declarationSymbol, {
importKind: AstImportKind.NamedImport,
modulePath: externalModulePath,
exportName: exportName
exportName: exportName,
isTypeOnly: ExportAnalyzer._getIsTypeOnly(importDeclaration)
});
}

Expand Down Expand Up @@ -563,7 +566,8 @@ export class ExportAnalyzer {
return this._fetchAstImport(declarationSymbol, {
importKind: AstImportKind.DefaultImport,
modulePath: externalModulePath,
exportName
exportName,
isTypeOnly: ExportAnalyzer._getIsTypeOnly(importDeclaration)
});
}

Expand Down Expand Up @@ -604,7 +608,8 @@ export class ExportAnalyzer {
return this._fetchAstImport(declarationSymbol, {
importKind: AstImportKind.EqualsImport,
modulePath: externalModuleName,
exportName: variableName
exportName: variableName,
isTypeOnly: false
});
}
}
Expand All @@ -624,6 +629,13 @@ export class ExportAnalyzer {
return undefined;
}

private static _getIsTypeOnly(importDeclaration: ts.ImportDeclaration): boolean {
if (importDeclaration.importClause) {
return !!importDeclaration.importClause.isTypeOnly;
}
return false;
}

private _getExportOfSpecifierAstModule(
exportName: string,
importOrExportDeclaration: ts.ImportDeclaration | ts.ExportDeclaration,
Expand Down Expand Up @@ -700,7 +712,8 @@ export class ExportAnalyzer {
return this._fetchAstImport(astSymbol.followedSymbol, {
importKind: AstImportKind.NamedImport,
modulePath: starExportedModule.externalModulePath,
exportName: exportName
exportName: exportName,
isTypeOnly: false
});
}

Expand Down Expand Up @@ -815,6 +828,12 @@ export class ExportAnalyzer {
addIfMissing: true
});
}
} else {
// If we encounter at least one import that does not use the type-only form,
// then the .d.ts rollup will NOT use "import type".
if (!options.isTypeOnly) {
astImport.isTypeOnlyEverywhere = false;
}
}

return astImport;
Expand Down
18 changes: 12 additions & 6 deletions apps/api-extractor/src/generators/DtsEmitHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,34 @@ export class DtsEmitHelpers {
collectorEntity: CollectorEntity,
astImport: AstImport
): void {
const importPrefix: string = astImport.isTypeOnlyEverywhere ? 'import type' : 'import';

switch (astImport.importKind) {
case AstImportKind.DefaultImport:
if (collectorEntity.nameForEmit !== astImport.exportName) {
stringWriter.write(`import { default as ${collectorEntity.nameForEmit} }`);
stringWriter.write(`${importPrefix} { default as ${collectorEntity.nameForEmit} }`);
} else {
stringWriter.write(`import ${astImport.exportName}`);
stringWriter.write(`${importPrefix} ${astImport.exportName}`);
}
stringWriter.writeLine(` from '${astImport.modulePath}';`);
break;
case AstImportKind.NamedImport:
if (collectorEntity.nameForEmit !== astImport.exportName) {
stringWriter.write(`import { ${astImport.exportName} as ${collectorEntity.nameForEmit} }`);
stringWriter.write(`${importPrefix} { ${astImport.exportName} as ${collectorEntity.nameForEmit} }`);
} else {
stringWriter.write(`import { ${astImport.exportName} }`);
stringWriter.write(`${importPrefix} { ${astImport.exportName} }`);
}
stringWriter.writeLine(` from '${astImport.modulePath}';`);
break;
case AstImportKind.StarImport:
stringWriter.writeLine(`import * as ${collectorEntity.nameForEmit} from '${astImport.modulePath}';`);
stringWriter.writeLine(
`${importPrefix} * as ${collectorEntity.nameForEmit} from '${astImport.modulePath}';`
);
break;
case AstImportKind.EqualsImport:
stringWriter.writeLine(`import ${collectorEntity.nameForEmit} = require('${astImport.modulePath}');`);
stringWriter.writeLine(
`${importPrefix} ${collectorEntity.nameForEmit} = require('${astImport.modulePath}');`
);
break;
default:
throw new InternalError('Unimplemented AstImportKind');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"exportStar3",
"functionOverload",
"importEquals",
"importType",
"inconsistentReleaseTags",
"internationalCharacters",
"preapproved",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
{
"metadata": {
"toolPackage": "@microsoft/api-extractor",
"toolVersion": "[test mode]",
"schemaVersion": 1003,
"oldestForwardsCompatibleVersion": 1001
},
"kind": "Package",
"canonicalReference": "api-extractor-scenarios!",
"docComment": "",
"name": "api-extractor-scenarios",
"members": [
{
"kind": "EntryPoint",
"canonicalReference": "api-extractor-scenarios!",
"name": "",
"members": [
{
"kind": "Interface",
"canonicalReference": "api-extractor-scenarios!A:interface",
"docComment": "/**\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "export interface A extends "
},
{
"kind": "Reference",
"text": "Lib1Class",
"canonicalReference": "api-extractor-lib1-test!Lib1Class:class"
},
{
"kind": "Content",
"text": " "
}
],
"releaseTag": "Public",
"name": "A",
"members": [],
"extendsTokenRanges": [
{
"startIndex": 1,
"endIndex": 3
}
]
},
{
"kind": "Interface",
"canonicalReference": "api-extractor-scenarios!B:interface",
"docComment": "/**\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "export interface B extends "
},
{
"kind": "Reference",
"text": "Lib1Interface",
"canonicalReference": "api-extractor-lib1-test!Lib1Interface:interface"
},
{
"kind": "Content",
"text": " "
}
],
"releaseTag": "Public",
"name": "B",
"members": [],
"extendsTokenRanges": [
{
"startIndex": 1,
"endIndex": 3
}
]
},
{
"kind": "Interface",
"canonicalReference": "api-extractor-scenarios!C:interface",
"docComment": "/**\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "export interface C extends "
},
{
"kind": "Reference",
"text": "Renamed",
"canonicalReference": "api-extractor-lib1-test!Lib1Interface:interface"
},
{
"kind": "Content",
"text": " "
}
],
"releaseTag": "Public",
"name": "C",
"members": [],
"extendsTokenRanges": [
{
"startIndex": 1,
"endIndex": 3
}
]
}
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## API Report File for "api-extractor-scenarios"

> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts

import type { Lib1Class } from 'api-extractor-lib1-test';
import { Lib1Interface } from 'api-extractor-lib1-test';

// @public (undocumented)
export interface A extends Lib1Class {
}

// @public (undocumented)
export interface B extends Lib1Interface {
}

// @public (undocumented)
export interface C extends Lib1Interface {
}


// (No @packageDocumentation comment for this package)

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Lib1Class } from 'api-extractor-lib1-test';
import { Lib1Interface } from 'api-extractor-lib1-test';

/** @public */
export declare interface A extends Lib1Class {
}

/** @public */
export declare interface B extends Lib1Interface {
}

/** @public */
export declare interface C extends Lib1Interface {
}

export { }
16 changes: 16 additions & 0 deletions build-tests/api-extractor-scenarios/src/importType/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import type { Lib1Class, Lib1Interface } from 'api-extractor-lib1-test';

// This should prevent Lib1Interface from being emitted as a type-only import, even though B uses it that way.
import { Lib1Interface as Renamed } from 'api-extractor-lib1-test';

/** @public */
export interface A extends Lib1Class {}

/** @public */
export interface B extends Lib1Interface {}

/** @public */
export interface C extends Renamed {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@microsoft/api-extractor",
"comment": "Add support for \"import type\" imports (new in TypeScript 3.8)",
"type": "minor"
}
],
"packageName": "@microsoft/api-extractor",
"email": "[email protected]"
}

0 comments on commit 743ca2e

Please sign in to comment.