Skip to content

Commit

Permalink
Use import types to refer to declarations in declaration emit (#24071)
Browse files Browse the repository at this point in the history
* Stand up a simple implementation using import types for exports of modules which are otherwise inaccessible

* Ensure references exist to link to modules containing utilized ambient modules

* Accept baselines with new import type usage

* Fix lint
  • Loading branch information
weswigham authored May 17, 2018
1 parent 09b9ec4 commit 3fc727b
Show file tree
Hide file tree
Showing 77 changed files with 1,154 additions and 1,637 deletions.
153 changes: 93 additions & 60 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

16 changes: 12 additions & 4 deletions src/compiler/transformers/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,23 @@ namespace ts {
let lateStatementReplacementMap: Map<VisitResult<LateVisibilityPaintedStatement>>;
let suppressNewDiagnosticContexts: boolean;

const host = context.getEmitHost();
const symbolTracker: SymbolTracker = {
trackSymbol,
reportInaccessibleThisError,
reportInaccessibleUniqueSymbolError,
reportPrivateInBaseOfClassExpression
reportPrivateInBaseOfClassExpression,
moduleResolverHost: host,
trackReferencedAmbientModule,
};
let errorNameNode: DeclarationName | undefined;

let currentSourceFile: SourceFile;
let refs: Map<SourceFile>;
const resolver = context.getEmitResolver();
const options = context.getCompilerOptions();
const newLine = getNewLineCharacter(options);
const { noResolve, stripInternal } = options;
const host = context.getEmitHost();
return transformRoot;

function recordTypeReferenceDirectivesIfNecessary(typeReferenceDirectives: string[]): void {
Expand All @@ -63,6 +66,11 @@ namespace ts {
}
}

function trackReferencedAmbientModule(node: ModuleDeclaration) {
const container = getSourceFileOfNode(node);
refs.set("" + getOriginalNodeId(container), container);
}

function handleSymbolAccessibilityError(symbolAccessibilityResult: SymbolAccessibilityResult) {
if (symbolAccessibilityResult.accessibility === SymbolAccessibility.Accessible) {
// Add aliases back onto the possible imports list if they're not there so we can try them again with updated visibility info
Expand Down Expand Up @@ -197,13 +205,13 @@ namespace ts {
lateMarkedStatements = undefined;
lateStatementReplacementMap = createMap();
necessaryTypeRefernces = undefined;
const refs = collectReferences(currentSourceFile, createMap());
refs = collectReferences(currentSourceFile, createMap());
const references: FileReference[] = [];
const outputFilePath = getDirectoryPath(normalizeSlashes(getOutputPathsFor(node, host, /*forceDtsPaths*/ true).declarationFilePath));
const referenceVisitor = mapReferencesIntoArray(references, outputFilePath);
refs.forEach(referenceVisitor);
const statements = visitNodes(node.statements, visitDeclarationStatements);
let combinedStatements = setTextRange(createNodeArray(transformAndReplaceLatePaintedStatements(statements)), node.statements);
refs.forEach(referenceVisitor);
const emittedImports = filter(combinedStatements, isAnyImportSyntax);
if (isExternalModule(node) && (!resultHasExternalModuleIndicator || (needsScopeFixMarker && !resultHasScopeMarker))) {
combinedStatements = setTextRange(createNodeArray([...combinedStatements, createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createNamedExports([]), /*moduleSpecifier*/ undefined)]), combinedStatements);
Expand Down
13 changes: 12 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3093,7 +3093,7 @@ namespace ts {
WriteArrayAsGenericType = 1 << 1, // Write Array<T> instead T[]
GenerateNamesForShadowedTypeParams = 1 << 2, // When a type parameter T is shadowing another T, generate a name for it so it can still be referenced
UseStructuralFallback = 1 << 3, // When an alias cannot be named by its symbol, rather than report an error, fallback to a structural printout if possible
// empty space
ForbidIndexedAccessSymbolReferences = 1 << 4, // Forbid references like `I["a"]["b"]` - print `typeof I.a<x>.b<y>` instead
WriteTypeArgumentsOfSignature = 1 << 5, // Write the type arguments instead of type parameters of the signature
UseFullyQualifiedType = 1 << 6, // Write out the fully qualified type name (eg. Module.Type, instead of Type)
UseOnlyExternalAliasing = 1 << 7, // Only use external aliases for a symbol
Expand Down Expand Up @@ -5258,6 +5258,13 @@ namespace ts {
isAtStartOfLine(): boolean;
}

/* @internal */
export interface ModuleNameResolverHost {
getCanonicalFileName(f: string): string;
getCommonSourceDirectory(): string;
getCurrentDirectory(): string;
}

/** @deprecated See comment on SymbolWriter */
// Note: this has non-deprecated internal uses.
export interface SymbolTracker {
Expand All @@ -5268,6 +5275,10 @@ namespace ts {
reportInaccessibleThisError?(): void;
reportPrivateInBaseOfClassExpression?(propertyName: string): void;
reportInaccessibleUniqueSymbolError?(): void;
/* @internal */
moduleResolverHost?: ModuleNameResolverHost;
/* @internal */
trackReferencedAmbientModule?(decl: ModuleDeclaration): void;
}

export interface TextSpan {
Expand Down
13 changes: 7 additions & 6 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2878,11 +2878,11 @@ namespace ts {
};
}

export function getResolvedExternalModuleName(host: EmitHost, file: SourceFile): string {
return file.moduleName || getExternalModuleNameFromPath(host, file.fileName);
export function getResolvedExternalModuleName(host: ModuleNameResolverHost, file: SourceFile, referenceFile?: SourceFile): string {
return file.moduleName || getExternalModuleNameFromPath(host, file.fileName, referenceFile && referenceFile.fileName);
}

export function getExternalModuleNameFromDeclaration(host: EmitHost, resolver: EmitResolver, declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration | ImportTypeNode): string {
export function getExternalModuleNameFromDeclaration(host: ModuleNameResolverHost, resolver: EmitResolver, declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration | ImportTypeNode): string {
const file = resolver.getExternalModuleFileFromDeclaration(declaration);
if (!file || file.isDeclarationFile) {
return undefined;
Expand All @@ -2893,12 +2893,13 @@ namespace ts {
/**
* Resolves a local path to a path which is absolute to the base of the emit
*/
export function getExternalModuleNameFromPath(host: EmitHost, fileName: string): string {
export function getExternalModuleNameFromPath(host: ModuleNameResolverHost, fileName: string, referencePath?: string): string {
const getCanonicalFileName = (f: string) => host.getCanonicalFileName(f);
const dir = toPath(host.getCommonSourceDirectory(), host.getCurrentDirectory(), getCanonicalFileName);
const dir = toPath(referencePath ? getDirectoryPath(referencePath) : host.getCommonSourceDirectory(), host.getCurrentDirectory(), getCanonicalFileName);
const filePath = getNormalizedAbsolutePath(fileName, host.getCurrentDirectory());
const relativePath = getRelativePathToDirectoryOrUrl(dir, filePath, dir, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
return removeFileExtension(relativePath);
const extensionless = removeFileExtension(relativePath);
return referencePath ? ensurePathIsNonModuleName(extensionless) : extensionless;
}

export function getOwnEmitOutputFilePath(sourceFile: SourceFile, host: EmitHost, extension: string) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
=== tests/cases/compiler/Class.ts ===
import { Configurable } from "./Configurable"
>Configurable : <T extends new (...args: any[]) => {}>(base: T) => T
>Configurable : <T extends import("tests/cases/compiler/Configurable").Constructor<{}>>(base: T) => T

export class HiddenClass {}
>HiddenClass : HiddenClass

export class ActualClass extends Configurable(HiddenClass) {}
>ActualClass : ActualClass
>Configurable(HiddenClass) : HiddenClass
>Configurable : <T extends new (...args: any[]) => {}>(base: T) => T
>Configurable : <T extends import("tests/cases/compiler/Configurable").Constructor<{}>>(base: T) => T
>HiddenClass : typeof HiddenClass

=== tests/cases/compiler/Configurable.ts ===
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1900,6 +1900,7 @@ declare namespace ts {
WriteArrayAsGenericType = 2,
GenerateNamesForShadowedTypeParams = 4,
UseStructuralFallback = 8,
ForbidIndexedAccessSymbolReferences = 16,
WriteTypeArgumentsOfSignature = 32,
UseFullyQualifiedType = 64,
UseOnlyExternalAliasing = 128,
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1900,6 +1900,7 @@ declare namespace ts {
WriteArrayAsGenericType = 2,
GenerateNamesForShadowedTypeParams = 4,
UseStructuralFallback = 8,
ForbidIndexedAccessSymbolReferences = 16,
WriteTypeArgumentsOfSignature = 32,
UseFullyQualifiedType = 64,
UseOnlyExternalAliasing = 128,
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/augmentExportEquals5.types
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ declare module "express" {
function e(): e.Express;
>e : typeof e
>e : any
>Express : Express
>Express : e.Express

namespace e {
>e : typeof e
Expand Down
21 changes: 3 additions & 18 deletions tests/baselines/reference/declarationEmitAliasFromIndirectFile.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,9 @@ exports["default"] = fp.l10ns;

//// [app.d.ts]
declare const _default: {
ar?: {
weekdays: {
shorthand: [string, string, string, string, string, string, string];
longhand: [string, string, string, string, string, string, string];
};
};
bg?: {
weekdays: {
shorthand: [string, string, string, string, string, string, string];
longhand: [string, string, string, string, string, string, string];
};
};
ar?: import("./locale").CustomLocale;
bg?: import("./locale").CustomLocale;
} & {
default: {
weekdays: {
shorthand: [string, string, string, string, string, string, string];
longhand: [string, string, string, string, string, string, string];
};
};
default: import("./locale").Locale;
};
export default _default;
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const fp = { l10ns: {} } as FlatpickrFn;
>FlatpickrFn : FlatpickrFn

export default fp.l10ns;
>fp.l10ns : { ar?: { weekdays: { shorthand: [string, string, string, string, string, string, string]; longhand: [string, string, string, string, string, string, string]; }; }; bg?: { weekdays: { shorthand: [string, string, string, string, string, string, string]; longhand: [string, string, string, string, string, string, string]; }; }; } & { default: { weekdays: { shorthand: [string, string, string, string, string, string, string]; longhand: [string, string, string, string, string, string, string]; }; }; }
>fp.l10ns : { ar?: import("tests/cases/compiler/locale").CustomLocale; bg?: import("tests/cases/compiler/locale").CustomLocale; } & { default: import("tests/cases/compiler/locale").Locale; }
>fp : FlatpickrFn
>l10ns : { ar?: { weekdays: { shorthand: [string, string, string, string, string, string, string]; longhand: [string, string, string, string, string, string, string]; }; }; bg?: { weekdays: { shorthand: [string, string, string, string, string, string, string]; longhand: [string, string, string, string, string, string, string]; }; }; } & { default: { weekdays: { shorthand: [string, string, string, string, string, string, string]; longhand: [string, string, string, string, string, string, string]; }; }; }
>l10ns : { ar?: import("tests/cases/compiler/locale").CustomLocale; bg?: import("tests/cases/compiler/locale").CustomLocale; } & { default: import("tests/cases/compiler/locale").Locale; }

Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ exports.v = v;
//// [0.d.ts]
export declare type Data = string | boolean;
//// [1.d.ts]
declare let v: string | boolean;
declare let v: import("./0").Data;
export { v };
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ let obj: Data = true;

=== tests/cases/compiler/1.ts ===
let v = "str" || true;
>v : string | boolean
>v : import("tests/cases/compiler/0").Data
>"str" || true : true | "str"
>"str" : "str"
>true : true

export { v }
>v : string | boolean
>v : import("tests/cases/compiler/0").Data

Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//// [tests/cases/compiler/declarationsForInferredTypeFromOtherFile.ts] ////

//// [file1.ts]
export class Foo {}
//// [file2.ts]
export function foo(): import("./file1").Foo {
return null as any;
}
//// [file3.ts]
import {foo} from "./file2";
export function bar() {
return foo();
}


//// [file1.js]
"use strict";
exports.__esModule = true;
var Foo = /** @class */ (function () {
function Foo() {
}
return Foo;
}());
exports.Foo = Foo;
//// [file2.js]
"use strict";
exports.__esModule = true;
function foo() {
return null;
}
exports.foo = foo;
//// [file3.js]
"use strict";
exports.__esModule = true;
var file2_1 = require("./file2");
function bar() {
return file2_1.foo();
}
exports.bar = bar;


//// [file1.d.ts]
export declare class Foo {
}
//// [file2.d.ts]
export declare function foo(): import("./file1").Foo;
//// [file3.d.ts]
export declare function bar(): import("./file1").Foo;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
=== tests/cases/compiler/file1.ts ===
export class Foo {}
>Foo : Symbol(Foo, Decl(file1.ts, 0, 0))

=== tests/cases/compiler/file2.ts ===
export function foo(): import("./file1").Foo {
>foo : Symbol(foo, Decl(file2.ts, 0, 0))
>Foo : Symbol(Foo, Decl(file1.ts, 0, 0))

return null as any;
}
=== tests/cases/compiler/file3.ts ===
import {foo} from "./file2";
>foo : Symbol(foo, Decl(file3.ts, 0, 8))

export function bar() {
>bar : Symbol(bar, Decl(file3.ts, 0, 28))

return foo();
>foo : Symbol(foo, Decl(file3.ts, 0, 8))
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
=== tests/cases/compiler/file1.ts ===
export class Foo {}
>Foo : Foo

=== tests/cases/compiler/file2.ts ===
export function foo(): import("./file1").Foo {
>foo : () => import("tests/cases/compiler/file1").Foo
>Foo : import("tests/cases/compiler/file1").Foo

return null as any;
>null as any : any
>null : null
}
=== tests/cases/compiler/file3.ts ===
import {foo} from "./file2";
>foo : () => import("tests/cases/compiler/file1").Foo

export function bar() {
>bar : () => import("tests/cases/compiler/file1").Foo

return foo();
>foo() : import("tests/cases/compiler/file1").Foo
>foo : () => import("tests/cases/compiler/file1").Foo
}

Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,39 @@ type Constructor = new (...args: any[]) => {};
>args : any[]

const Mixin1 = <C extends Constructor>(Base: C) => class extends Base { private _fooPrivate: {}; }
>Mixin1 : <C extends Constructor>(Base: C) => { new (...args: any[]): (Anonymous class); prototype: <any>.(Anonymous class); } & C
><C extends Constructor>(Base: C) => class extends Base { private _fooPrivate: {}; } : <C extends Constructor>(Base: C) => { new (...args: any[]): (Anonymous class); prototype: <any>.(Anonymous class); } & C
>Mixin1 : <C extends Constructor>(Base: C) => { new (...args: any[]): (Anonymous class); prototype: Mixin1<any>.(Anonymous class); } & C
><C extends Constructor>(Base: C) => class extends Base { private _fooPrivate: {}; } : <C extends Constructor>(Base: C) => { new (...args: any[]): (Anonymous class); prototype: Mixin1<any>.(Anonymous class); } & C
>C : C
>Constructor : Constructor
>Base : C
>C : C
>class extends Base { private _fooPrivate: {}; } : { new (...args: any[]): (Anonymous class); prototype: <any>.(Anonymous class); } & C
>class extends Base { private _fooPrivate: {}; } : { new (...args: any[]): (Anonymous class); prototype: Mixin1<any>.(Anonymous class); } & C
>Base : {}
>_fooPrivate : {}

type FooConstructor = typeof Mixin1 extends (a: Constructor) => infer Cls ? Cls : never;
>FooConstructor : { new (...args: any[]): <Constructor>.(Anonymous class); prototype: <any>.(Anonymous class); } & Constructor
>Mixin1 : <C extends Constructor>(Base: C) => { new (...args: any[]): (Anonymous class); prototype: <any>.(Anonymous class); } & C
>FooConstructor : { new (...args: any[]): Mixin1<Constructor>.(Anonymous class); prototype: Mixin1<any>.(Anonymous class); } & Constructor
>Mixin1 : <C extends Constructor>(Base: C) => { new (...args: any[]): (Anonymous class); prototype: Mixin1<any>.(Anonymous class); } & C
>a : Constructor
>Constructor : Constructor
>Cls : Cls
>Cls : Cls

const Mixin2 = <C extends FooConstructor>(Base: C) => class extends Base {};
>Mixin2 : <C extends { new (...args: any[]): <Constructor>.(Anonymous class); prototype: <any>.(Anonymous class); } & Constructor>(Base: C) => { new (...args: any[]): (Anonymous class); prototype: <any>.(Anonymous class); } & C
><C extends FooConstructor>(Base: C) => class extends Base {} : <C extends { new (...args: any[]): <Constructor>.(Anonymous class); prototype: <any>.(Anonymous class); } & Constructor>(Base: C) => { new (...args: any[]): (Anonymous class); prototype: <any>.(Anonymous class); } & C
>Mixin2 : <C extends { new (...args: any[]): Mixin1<Constructor>.(Anonymous class); prototype: Mixin1<any>.(Anonymous class); } & Constructor>(Base: C) => { new (...args: any[]): (Anonymous class); prototype: Mixin2<any>.(Anonymous class); } & C
><C extends FooConstructor>(Base: C) => class extends Base {} : <C extends { new (...args: any[]): Mixin1<Constructor>.(Anonymous class); prototype: Mixin1<any>.(Anonymous class); } & Constructor>(Base: C) => { new (...args: any[]): (Anonymous class); prototype: Mixin2<any>.(Anonymous class); } & C
>C : C
>FooConstructor : { new (...args: any[]): <Constructor>.(Anonymous class); prototype: <any>.(Anonymous class); } & Constructor
>FooConstructor : { new (...args: any[]): Mixin1<Constructor>.(Anonymous class); prototype: Mixin1<any>.(Anonymous class); } & Constructor
>Base : C
>C : C
>class extends Base {} : { new (...args: any[]): (Anonymous class); prototype: <any>.(Anonymous class); } & C
>Base : <Constructor>.(Anonymous class)
>class extends Base {} : { new (...args: any[]): (Anonymous class); prototype: Mixin2<any>.(Anonymous class); } & C
>Base : Mixin1<Constructor>.(Anonymous class)

class C extends Mixin2(Mixin1(Object)) {}
>C : C
>Mixin2(Mixin1(Object)) : <{ new (...args: any[]): <ObjectConstructor>.(Anonymous class); prototype: <any>.(Anonymous class); } & ObjectConstructor>.(Anonymous class) & <ObjectConstructor>.(Anonymous class) & Object
>Mixin2 : <C extends { new (...args: any[]): <Constructor>.(Anonymous class); prototype: <any>.(Anonymous class); } & Constructor>(Base: C) => { new (...args: any[]): (Anonymous class); prototype: <any>.(Anonymous class); } & C
>Mixin1(Object) : { new (...args: any[]): <ObjectConstructor>.(Anonymous class); prototype: <any>.(Anonymous class); } & ObjectConstructor
>Mixin1 : <C extends Constructor>(Base: C) => { new (...args: any[]): (Anonymous class); prototype: <any>.(Anonymous class); } & C
>Mixin2(Mixin1(Object)) : Mixin2<{ new (...args: any[]): Mixin1<ObjectConstructor>.(Anonymous class); prototype: Mixin1<any>.(Anonymous class); } & ObjectConstructor>.(Anonymous class) & Mixin1<ObjectConstructor>.(Anonymous class) & Object
>Mixin2 : <C extends { new (...args: any[]): Mixin1<Constructor>.(Anonymous class); prototype: Mixin1<any>.(Anonymous class); } & Constructor>(Base: C) => { new (...args: any[]): (Anonymous class); prototype: Mixin2<any>.(Anonymous class); } & C
>Mixin1(Object) : { new (...args: any[]): Mixin1<ObjectConstructor>.(Anonymous class); prototype: Mixin1<any>.(Anonymous class); } & ObjectConstructor
>Mixin1 : <C extends Constructor>(Base: C) => { new (...args: any[]): (Anonymous class); prototype: Mixin1<any>.(Anonymous class); } & C
>Object : ObjectConstructor

Loading

0 comments on commit 3fc727b

Please sign in to comment.