Skip to content

Commit 414bc49

Browse files
authored
Module or import types (#22592)
* Type side of import types * Value side of import types * Accept library changes * Refined implementation, more tests * Allow resolutions to be performed late if the resolution still results in a file already in the build * Add another test case * Add some jsdoc usages * Allow nodebuilder to use import types where appropriate * Parse & check generic instantiations * use import types in nodebuilder for typeof module symbols * Wire up go to definition for import types * Accept updated type/symbol baselines now that symbols are wired in * PR feedback * Fix changes from merge * Walk back late import handling * Remove unused diagnostic * Remove unrelated changes * Use recursive function over loop * Emit type arguments * undo unrelated change * Test for and support import type nodes in bundled declaration emit
1 parent 5c44241 commit 414bc49

File tree

287 files changed

+3825
-1278
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

287 files changed

+3825
-1278
lines changed

src/compiler/checker.ts

Lines changed: 163 additions & 25 deletions
Large diffs are not rendered by default.

src/compiler/diagnosticMessages.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -955,6 +955,18 @@
955955
"category": "Error",
956956
"code": 1338
957957
},
958+
"Module '{0}' does not refer to a value, but is used as a value here.": {
959+
"category": "Error",
960+
"code": 1339
961+
},
962+
"Module '{0}' does not refer to a type, but is used as a type here.": {
963+
"category": "Error",
964+
"code": 1340
965+
},
966+
"Type arguments cannot be used here.": {
967+
"category": "Error",
968+
"code": 1342
969+
},
958970

959971
"Duplicate identifier '{0}'.": {
960972
"category": "Error",

src/compiler/emitter.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,8 @@ namespace ts {
615615
return emitMappedType(<MappedTypeNode>node);
616616
case SyntaxKind.LiteralType:
617617
return emitLiteralType(<LiteralTypeNode>node);
618+
case SyntaxKind.ImportTypeNode:
619+
return emitImportTypeNode(<ImportTypeNode>node);
618620
case SyntaxKind.JSDocAllType:
619621
write("*");
620622
return;
@@ -1327,6 +1329,22 @@ namespace ts {
13271329
emitExpression(node.literal);
13281330
}
13291331

1332+
function emitImportTypeNode(node: ImportTypeNode) {
1333+
if (node.isTypeOf) {
1334+
writeKeyword("typeof");
1335+
writeSpace();
1336+
}
1337+
writeKeyword("import");
1338+
writePunctuation("(");
1339+
emit(node.argument);
1340+
writePunctuation(")");
1341+
if (node.qualifier) {
1342+
writePunctuation(".");
1343+
emit(node.qualifier);
1344+
}
1345+
emitTypeArguments(node, node.typeArguments);
1346+
}
1347+
13301348
//
13311349
// Binding patterns
13321350
//

src/compiler/factory.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,24 @@ namespace ts {
809809
: node;
810810
}
811811

812+
export function createImportTypeNode(argument: TypeNode, qualifier?: EntityName, typeArguments?: ReadonlyArray<TypeNode>, isTypeOf?: boolean) {
813+
const node = <ImportTypeNode>createSynthesizedNode(SyntaxKind.ImportTypeNode);
814+
node.argument = argument;
815+
node.qualifier = qualifier;
816+
node.typeArguments = asNodeArray(typeArguments);
817+
node.isTypeOf = isTypeOf;
818+
return node;
819+
}
820+
821+
export function updateImportTypeNode(node: ImportTypeNode, argument: TypeNode, qualifier?: EntityName, typeArguments?: ReadonlyArray<TypeNode>, isTypeOf?: boolean) {
822+
return node.argument !== argument
823+
|| node.qualifier !== qualifier
824+
|| node.typeArguments !== typeArguments
825+
|| node.isTypeOf !== isTypeOf
826+
? updateNode(createImportTypeNode(argument, qualifier, typeArguments, isTypeOf), node)
827+
: node;
828+
}
829+
812830
export function createParenthesizedType(type: TypeNode) {
813831
const node = <ParenthesizedTypeNode>createSynthesizedNode(SyntaxKind.ParenthesizedType);
814832
node.type = type;

src/compiler/parser.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,10 @@ namespace ts {
189189
visitNode(cbNode, (<ConditionalTypeNode>node).falseType);
190190
case SyntaxKind.InferType:
191191
return visitNode(cbNode, (<InferTypeNode>node).typeParameter);
192+
case SyntaxKind.ImportTypeNode:
193+
return visitNode(cbNode, (<ImportTypeNode>node).argument) ||
194+
visitNode(cbNode, (<ImportTypeNode>node).qualifier) ||
195+
visitNodes(cbNode, cbNodes, (<ImportTypeNode>node).typeArguments);
192196
case SyntaxKind.ParenthesizedType:
193197
case SyntaxKind.TypeOperator:
194198
return visitNode(cbNode, (<ParenthesizedTypeNode | TypeOperatorNode>node).type);
@@ -2733,6 +2737,28 @@ namespace ts {
27332737
return finishNode(node);
27342738
}
27352739

2740+
function isStartOfTypeOfImportType() {
2741+
nextToken();
2742+
return token() === SyntaxKind.ImportKeyword;
2743+
}
2744+
2745+
function parseImportType(): ImportTypeNode {
2746+
sourceFile.flags |= NodeFlags.PossiblyContainsDynamicImport;
2747+
const node = createNode(SyntaxKind.ImportTypeNode) as ImportTypeNode;
2748+
if (parseOptional(SyntaxKind.TypeOfKeyword)) {
2749+
node.isTypeOf = true;
2750+
}
2751+
parseExpected(SyntaxKind.ImportKeyword);
2752+
parseExpected(SyntaxKind.OpenParenToken);
2753+
node.argument = parseType();
2754+
parseExpected(SyntaxKind.CloseParenToken);
2755+
if (parseOptional(SyntaxKind.DotToken)) {
2756+
node.qualifier = parseEntityName(/*allowReservedWords*/ true, Diagnostics.Type_expected);
2757+
}
2758+
node.typeArguments = tryParseTypeArguments();
2759+
return finishNode(node);
2760+
}
2761+
27362762
function nextTokenIsNumericLiteral() {
27372763
return nextToken() === SyntaxKind.NumericLiteral;
27382764
}
@@ -2780,13 +2806,15 @@ namespace ts {
27802806
}
27812807
}
27822808
case SyntaxKind.TypeOfKeyword:
2783-
return parseTypeQuery();
2809+
return lookAhead(isStartOfTypeOfImportType) ? parseImportType() : parseTypeQuery();
27842810
case SyntaxKind.OpenBraceToken:
27852811
return lookAhead(isStartOfMappedType) ? parseMappedType() : parseTypeLiteral();
27862812
case SyntaxKind.OpenBracketToken:
27872813
return parseTupleType();
27882814
case SyntaxKind.OpenParenToken:
27892815
return parseParenthesizedType();
2816+
case SyntaxKind.ImportKeyword:
2817+
return parseImportType();
27902818
default:
27912819
return parseTypeReference();
27922820
}
@@ -2822,6 +2850,7 @@ namespace ts {
28222850
case SyntaxKind.ExclamationToken:
28232851
case SyntaxKind.DotDotDotToken:
28242852
case SyntaxKind.InferKeyword:
2853+
case SyntaxKind.ImportKeyword:
28252854
return true;
28262855
case SyntaxKind.MinusToken:
28272856
return !inStartOfParameter && lookAhead(nextTokenIsNumericLiteral);

src/compiler/program.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1686,10 +1686,20 @@ namespace ts {
16861686
else if (isImportCall(node) && node.arguments.length === 1 && isStringLiteralLike(node.arguments[0])) {
16871687
imports = append(imports, node.arguments[0] as StringLiteralLike);
16881688
}
1689+
else if (isLiteralImportTypeNode(node)) {
1690+
imports = append(imports, node.argument.literal);
1691+
}
16891692
else {
1690-
forEachChild(node, collectDynamicImportOrRequireCalls);
1693+
collectDynamicImportOrRequireCallsForEachChild(node);
1694+
if (hasJSDocNodes(node)) {
1695+
forEach(node.jsDoc, collectDynamicImportOrRequireCallsForEachChild);
1696+
}
16911697
}
16921698
}
1699+
1700+
function collectDynamicImportOrRequireCallsForEachChild(node: Node) {
1701+
forEachChild(node, collectDynamicImportOrRequireCalls);
1702+
}
16931703
}
16941704

16951705
/** This should have similar behavior to 'processSourceFile' without diagnostics or mutation. */

src/compiler/transformers/declarations.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -450,9 +450,9 @@ namespace ts {
450450
return setCommentRange(updated, getCommentRange(original));
451451
}
452452

453-
function rewriteModuleSpecifier<T extends Node>(parent: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration, input: T): T | StringLiteral {
453+
function rewriteModuleSpecifier<T extends Node>(parent: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration | ImportTypeNode, input: T): T | StringLiteral {
454454
if (!input) return;
455-
resultHasExternalModuleIndicator = resultHasExternalModuleIndicator || parent.kind !== SyntaxKind.ModuleDeclaration;
455+
resultHasExternalModuleIndicator = resultHasExternalModuleIndicator || (parent.kind !== SyntaxKind.ModuleDeclaration && parent.kind !== SyntaxKind.ImportTypeNode);
456456
if (input.kind === SyntaxKind.StringLiteral && isBundledEmit) {
457457
const newName = getExternalModuleNameFromDeclaration(context.getEmitHost(), resolver, parent);
458458
if (newName) {
@@ -765,6 +765,16 @@ namespace ts {
765765
case SyntaxKind.ConstructorType: {
766766
return cleanup(updateConstructorTypeNode(input, visitNodes(input.typeParameters, visitDeclarationSubtree), updateParamsList(input, input.parameters), visitNode(input.type, visitDeclarationSubtree)));
767767
}
768+
case SyntaxKind.ImportTypeNode: {
769+
if (!isLiteralImportTypeNode(input)) return cleanup(input);
770+
return cleanup(updateImportTypeNode(
771+
input,
772+
updateLiteralTypeNode(input.argument, rewriteModuleSpecifier(input, input.argument.literal)),
773+
input.qualifier,
774+
visitNodes(input.typeArguments, visitDeclarationSubtree, isTypeNode),
775+
input.isTypeOf
776+
));
777+
}
768778
default: Debug.assertNever(input, `Attempted to process unhandled node kind: ${(ts as any).SyntaxKind[(input as any).kind]}`);
769779
}
770780
}
@@ -1264,7 +1274,8 @@ namespace ts {
12641274
| TypeReferenceNode
12651275
| ConditionalTypeNode
12661276
| FunctionTypeNode
1267-
| ConstructorTypeNode;
1277+
| ConstructorTypeNode
1278+
| ImportTypeNode;
12681279

12691280
function isProcessedComponent(node: Node): node is ProcessedComponent {
12701281
switch (node.kind) {
@@ -1285,6 +1296,7 @@ namespace ts {
12851296
case SyntaxKind.ConditionalType:
12861297
case SyntaxKind.FunctionType:
12871298
case SyntaxKind.ConstructorType:
1299+
case SyntaxKind.ImportTypeNode:
12881300
return true;
12891301
}
12901302
return false;

src/compiler/types.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ namespace ts {
284284
IndexedAccessType,
285285
MappedType,
286286
LiteralType,
287+
ImportTypeNode,
287288
// Binding patterns
288289
ObjectBindingPattern,
289290
ArrayBindingPattern,
@@ -445,7 +446,7 @@ namespace ts {
445446
FirstFutureReservedWord = ImplementsKeyword,
446447
LastFutureReservedWord = YieldKeyword,
447448
FirstTypeNode = TypePredicate,
448-
LastTypeNode = LiteralType,
449+
LastTypeNode = ImportTypeNode,
449450
FirstPunctuation = OpenBraceToken,
450451
LastPunctuation = CaretEqualsToken,
451452
FirstToken = Unknown,
@@ -1067,6 +1068,16 @@ namespace ts {
10671068
| SyntaxKind.NeverKeyword;
10681069
}
10691070

1071+
export interface ImportTypeNode extends NodeWithTypeArguments {
1072+
kind: SyntaxKind.ImportTypeNode;
1073+
isTypeOf?: boolean;
1074+
argument: TypeNode;
1075+
qualifier?: EntityName;
1076+
}
1077+
1078+
/* @internal */
1079+
export type LiteralImportTypeNode = ImportTypeNode & { argument: LiteralTypeNode & { literal: StringLiteral } };
1080+
10701081
export interface ThisTypeNode extends TypeNode {
10711082
kind: SyntaxKind.ThisType;
10721083
}
@@ -1081,12 +1092,15 @@ namespace ts {
10811092
kind: SyntaxKind.ConstructorType;
10821093
}
10831094

1095+
export interface NodeWithTypeArguments extends TypeNode {
1096+
typeArguments?: NodeArray<TypeNode>;
1097+
}
1098+
10841099
export type TypeReferenceType = TypeReferenceNode | ExpressionWithTypeArguments;
10851100

1086-
export interface TypeReferenceNode extends TypeNode {
1101+
export interface TypeReferenceNode extends NodeWithTypeArguments {
10871102
kind: SyntaxKind.TypeReference;
10881103
typeName: EntityName;
1089-
typeArguments?: NodeArray<TypeNode>;
10901104
}
10911105

10921106
export interface TypePredicateNode extends TypeNode {
@@ -1696,11 +1710,10 @@ namespace ts {
16961710
expression: ImportExpression;
16971711
}
16981712

1699-
export interface ExpressionWithTypeArguments extends TypeNode {
1713+
export interface ExpressionWithTypeArguments extends NodeWithTypeArguments {
17001714
kind: SyntaxKind.ExpressionWithTypeArguments;
17011715
parent?: HeritageClause;
17021716
expression: LeftHandSideExpression;
1703-
typeArguments?: NodeArray<TypeNode>;
17041717
}
17051718

17061719
export interface NewExpression extends PrimaryExpression, Declaration {
@@ -3244,7 +3257,7 @@ namespace ts {
32443257
isOptionalParameter(node: ParameterDeclaration): boolean;
32453258
moduleExportsSomeValue(moduleReferenceExpression: Expression): boolean;
32463259
isArgumentsLocalBinding(node: Identifier): boolean;
3247-
getExternalModuleFileFromDeclaration(declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration): SourceFile;
3260+
getExternalModuleFileFromDeclaration(declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration | ImportTypeNode): SourceFile;
32483261
getTypeReferenceDirectivesForEntityName(name: EntityNameOrEntityNameExpression): string[];
32493262
getTypeReferenceDirectivesForSymbol(symbol: Symbol, meaning?: SymbolFlags): string[];
32503263
isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean;

src/compiler/utilities.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,12 @@ namespace ts {
734734
return n.kind === SyntaxKind.CallExpression && (<CallExpression>n).expression.kind === SyntaxKind.ImportKeyword;
735735
}
736736

737+
export function isLiteralImportTypeNode(n: Node): n is LiteralImportTypeNode {
738+
return n.kind === SyntaxKind.ImportTypeNode &&
739+
(n as ImportTypeNode).argument.kind === SyntaxKind.LiteralType &&
740+
isStringLiteral(((n as ImportTypeNode).argument as LiteralTypeNode).literal);
741+
}
742+
737743
export function isPrologueDirective(node: Node): node is PrologueDirective {
738744
return node.kind === SyntaxKind.ExpressionStatement
739745
&& (<ExpressionStatement>node).expression.kind === SyntaxKind.StringLiteral;
@@ -1697,13 +1703,15 @@ namespace ts {
16971703
}
16981704
}
16991705

1700-
export function getExternalModuleName(node: AnyImportOrReExport): Expression {
1706+
export function getExternalModuleName(node: AnyImportOrReExport | ImportTypeNode): Expression {
17011707
switch (node.kind) {
17021708
case SyntaxKind.ImportDeclaration:
17031709
case SyntaxKind.ExportDeclaration:
17041710
return node.moduleSpecifier;
17051711
case SyntaxKind.ImportEqualsDeclaration:
17061712
return node.moduleReference.kind === SyntaxKind.ExternalModuleReference ? node.moduleReference.expression : undefined;
1713+
case SyntaxKind.ImportTypeNode:
1714+
return isLiteralImportTypeNode(node) ? node.argument.literal : undefined;
17071715
default:
17081716
return Debug.assertNever(node);
17091717
}
@@ -2809,7 +2817,7 @@ namespace ts {
28092817
return file.moduleName || getExternalModuleNameFromPath(host, file.fileName);
28102818
}
28112819

2812-
export function getExternalModuleNameFromDeclaration(host: EmitHost, resolver: EmitResolver, declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration): string {
2820+
export function getExternalModuleNameFromDeclaration(host: EmitHost, resolver: EmitResolver, declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration | ImportTypeNode): string {
28132821
const file = resolver.getExternalModuleFileFromDeclaration(declaration);
28142822
if (!file || file.isDeclarationFile) {
28152823
return undefined;

src/compiler/visitor.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,14 @@ namespace ts {
398398
return updateInferTypeNode(<InferTypeNode>node,
399399
visitNode((<InferTypeNode>node).typeParameter, visitor, isTypeParameterDeclaration));
400400

401+
case SyntaxKind.ImportTypeNode:
402+
return updateImportTypeNode(<ImportTypeNode>node,
403+
visitNode((<ImportTypeNode>node).argument, visitor, isTypeNode),
404+
visitNode((<ImportTypeNode>node).qualifier, visitor, isEntityName),
405+
visitNodes((<ImportTypeNode>node).typeArguments, visitor, isTypeNode),
406+
(<ImportTypeNode>node).isTypeOf
407+
);
408+
401409
case SyntaxKind.ParenthesizedType:
402410
return updateParenthesizedType(<ParenthesizedTypeNode>node,
403411
visitNode((<ParenthesizedTypeNode>node).type, visitor, isTypeNode));

tests/baselines/reference/aliasAssignments.errors.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
tests/cases/compiler/aliasAssignments_1.ts(3,1): error TS2322: Type '1' is not assignable to type 'typeof "tests/cases/compiler/aliasAssignments_moduleA"'.
2-
tests/cases/compiler/aliasAssignments_1.ts(5,1): error TS2322: Type 'typeof "tests/cases/compiler/aliasAssignments_moduleA"' is not assignable to type 'number'.
1+
tests/cases/compiler/aliasAssignments_1.ts(3,1): error TS2322: Type '1' is not assignable to type 'typeof import("tests/cases/compiler/aliasAssignments_moduleA")'.
2+
tests/cases/compiler/aliasAssignments_1.ts(5,1): error TS2322: Type 'typeof import("tests/cases/compiler/aliasAssignments_moduleA")' is not assignable to type 'number'.
33

44

55
==== tests/cases/compiler/aliasAssignments_1.ts (2 errors) ====
66
import moduleA = require("./aliasAssignments_moduleA");
77
var x = moduleA;
88
x = 1; // Should be error
99
~
10-
!!! error TS2322: Type '1' is not assignable to type 'typeof "tests/cases/compiler/aliasAssignments_moduleA"'.
10+
!!! error TS2322: Type '1' is not assignable to type 'typeof import("tests/cases/compiler/aliasAssignments_moduleA")'.
1111
var y = 1;
1212
y = moduleA; // should be error
1313
~
14-
!!! error TS2322: Type 'typeof "tests/cases/compiler/aliasAssignments_moduleA"' is not assignable to type 'number'.
14+
!!! error TS2322: Type 'typeof import("tests/cases/compiler/aliasAssignments_moduleA")' is not assignable to type 'number'.
1515

1616
==== tests/cases/compiler/aliasAssignments_moduleA.ts (0 errors) ====
1717
export class someClass {

tests/baselines/reference/aliasOnMergedModuleInterface.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ var x: foo.A = foo.bar("hello"); // foo.A should be ok but foo.bar should be err
2626

2727
=== tests/cases/compiler/aliasOnMergedModuleInterface_0.ts ===
2828
declare module "foo"
29-
>"foo" : typeof "foo"
29+
>"foo" : typeof import("foo")
3030
{
3131
module B {
3232
>B : any

tests/baselines/reference/allowSyntheticDefaultImports10.errors.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
tests/cases/compiler/a.ts(2,5): error TS2339: Property 'default' does not exist on type 'typeof "tests/cases/compiler/b"'.
2-
tests/cases/compiler/a.ts(3,5): error TS2339: Property 'default' does not exist on type 'typeof "tests/cases/compiler/b"'.
1+
tests/cases/compiler/a.ts(2,5): error TS2339: Property 'default' does not exist on type 'typeof import("tests/cases/compiler/b")'.
2+
tests/cases/compiler/a.ts(3,5): error TS2339: Property 'default' does not exist on type 'typeof import("tests/cases/compiler/b")'.
33

44

55
==== tests/cases/compiler/a.ts (2 errors) ====
66
import Foo = require("./b");
77
Foo.default.bar();
88
~~~~~~~
9-
!!! error TS2339: Property 'default' does not exist on type 'typeof "tests/cases/compiler/b"'.
9+
!!! error TS2339: Property 'default' does not exist on type 'typeof import("tests/cases/compiler/b")'.
1010
Foo.default.default.foo();
1111
~~~~~~~
12-
!!! error TS2339: Property 'default' does not exist on type 'typeof "tests/cases/compiler/b"'.
12+
!!! error TS2339: Property 'default' does not exist on type 'typeof import("tests/cases/compiler/b")'.
1313
==== tests/cases/compiler/b.d.ts (0 errors) ====
1414
export function foo();
1515

0 commit comments

Comments
 (0)