Skip to content

Commit 953e54b

Browse files
committed
feat(41825): add importType jsdoc tag
1 parent 1135377 commit 953e54b

32 files changed

+553
-32
lines changed

src/compiler/checker.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import {
1111
AmbientModuleDeclaration,
1212
and,
1313
AnonymousType,
14+
AnyImportOrJsDocImportTypeImport,
1415
AnyImportOrReExport,
15-
AnyImportSyntax,
1616
append,
1717
appendIfUnique,
1818
ArrayBindingPattern,
@@ -583,6 +583,7 @@ import {
583583
isJSDocCallbackTag,
584584
isJSDocConstructSignature,
585585
isJSDocFunctionType,
586+
isJSDocImportTypeTag,
586587
isJSDocIndexSignature,
587588
isJSDocLinkLike,
588589
isJSDocMemberName,
@@ -770,6 +771,7 @@ import {
770771
JSDocEnumTag,
771772
JSDocFunctionType,
772773
JSDocImplementsTag,
774+
JSDocImportTypeTag,
773775
JSDocLink,
774776
JSDocLinkCode,
775777
JSDocLinkPlain,
@@ -3942,7 +3944,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
39423944
|| (n === stopAt || isFunctionLike(n) && (!getImmediatelyInvokedFunctionExpression(n) || (getFunctionFlags(n) & FunctionFlags.AsyncGenerator)) ? "quit" : false));
39433945
}
39443946

3945-
function getAnyImportSyntax(node: Node): AnyImportSyntax | undefined {
3947+
function getAnyImportSyntax(node: Node): AnyImportOrJsDocImportTypeImport | undefined {
39463948
switch (node.kind) {
39473949
case SyntaxKind.ImportEqualsDeclaration:
39483950
return node as ImportEqualsDeclaration;
@@ -4280,8 +4282,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
42804282
}
42814283
}
42824284

4283-
function getExternalModuleMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration, specifier: ImportOrExportSpecifier | BindingElement | PropertyAccessExpression, dontResolveAlias = false): Symbol | undefined {
4284-
const moduleSpecifier = getExternalModuleRequireArgument(node) || (node as ImportDeclaration | ExportDeclaration).moduleSpecifier!;
4285+
function getExternalModuleMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration | JSDocImportTypeTag, specifier: ImportOrExportSpecifier | BindingElement | PropertyAccessExpression, dontResolveAlias = false): Symbol | undefined {
4286+
const moduleSpecifier = getExternalModuleRequireArgument(node) || (node as ImportDeclaration | ExportDeclaration | JSDocImportTypeTag).moduleSpecifier!;
42854287
const moduleSymbol = resolveExternalModuleName(node, moduleSpecifier)!; // TODO: GH#18217
42864288
const name = !isPropertyAccessExpression(specifier) && specifier.propertyName || specifier.name;
42874289
if (!isIdentifier(name)) {
@@ -5006,6 +5008,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
50065008
? location
50075009
: (isModuleDeclaration(location) ? location : location.parent && isModuleDeclaration(location.parent) && location.parent.name === location ? location.parent : undefined)?.name ||
50085010
(isLiteralImportTypeNode(location) ? location : undefined)?.argument.literal ||
5011+
(isInJSFile(location) && isJSDocImportTypeTag(location) ? location.moduleSpecifier : undefined) ||
50095012
(isVariableDeclaration(location) && location.initializer && isRequireCall(location.initializer, /*requireStringLiteralLikeArgument*/ true) ? location.initializer.arguments[0] : undefined) ||
50105013
findAncestor(location, isImportCall)?.arguments[0] ||
50115014
findAncestor(location, isImportDeclaration)?.moduleSpecifier ||
@@ -9720,12 +9723,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
97209723
case SyntaxKind.ImportClause: {
97219724
const generatedSpecifier = getSpecifierForModuleSymbol(target.parent || target, context); // generate specifier (even though we're reusing and existing one) for ambient module reference include side effects
97229725
const specifier = bundled ? factory.createStringLiteral(generatedSpecifier) : (node as ImportClause).parent.moduleSpecifier;
9726+
const attributes = isImportDeclaration(node.parent) ? node.parent.attributes : undefined;
97239727
addResult(
97249728
factory.createImportDeclaration(
97259729
/*modifiers*/ undefined,
97269730
factory.createImportClause(/*isTypeOnly*/ false, factory.createIdentifier(localName), /*namedBindings*/ undefined),
97279731
specifier,
9728-
(node as ImportClause).parent.attributes,
9732+
attributes,
97299733
),
97309734
ModifierFlags.None,
97319735
);
@@ -9734,12 +9738,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
97349738
case SyntaxKind.NamespaceImport: {
97359739
const generatedSpecifier = getSpecifierForModuleSymbol(target.parent || target, context); // generate specifier (even though we're reusing and existing one) for ambient module reference include side effects
97369740
const specifier = bundled ? factory.createStringLiteral(generatedSpecifier) : (node as NamespaceImport).parent.parent.moduleSpecifier;
9741+
const attributes = isImportDeclaration(node.parent) ? node.parent.attributes : undefined;
97379742
addResult(
97389743
factory.createImportDeclaration(
97399744
/*modifiers*/ undefined,
97409745
factory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, factory.createNamespaceImport(factory.createIdentifier(localName))),
97419746
specifier,
9742-
(node as ImportClause).parent.attributes,
9747+
attributes,
97439748
),
97449749
ModifierFlags.None,
97459750
);
@@ -9759,6 +9764,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
97599764
case SyntaxKind.ImportSpecifier: {
97609765
const generatedSpecifier = getSpecifierForModuleSymbol(target.parent || target, context); // generate specifier (even though we're reusing and existing one) for ambient module reference include side effects
97619766
const specifier = bundled ? factory.createStringLiteral(generatedSpecifier) : (node as ImportSpecifier).parent.parent.parent.moduleSpecifier;
9767+
const attributes = isImportDeclaration(node.parent.parent.parent) ? node.parent.parent.parent.attributes : undefined;
97629768
addResult(
97639769
factory.createImportDeclaration(
97649770
/*modifiers*/ undefined,
@@ -9774,7 +9780,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
97749780
]),
97759781
),
97769782
specifier,
9777-
(node as ImportSpecifier).parent.parent.parent.attributes,
9783+
attributes,
97789784
),
97799785
ModifierFlags.None,
97809786
);

src/compiler/emitter.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ import {
266266
JSDocEnumTag,
267267
JSDocFunctionType,
268268
JSDocImplementsTag,
269+
JSDocImportTypeTag,
269270
JSDocNameReference,
270271
JSDocNonNullableType,
271272
JSDocNullableType,
@@ -2204,6 +2205,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
22042205
return emitJSDocTypedefTag(node as JSDocTypedefTag);
22052206
case SyntaxKind.JSDocSeeTag:
22062207
return emitJSDocSeeTag(node as JSDocSeeTag);
2208+
case SyntaxKind.JSDocImportTypeTag:
2209+
return emitJSDocImportTypeTag(node as JSDocImportTypeTag);
22072210
// SyntaxKind.JSDocPropertyTag (see JSDocParameterTag, above)
22082211

22092212
// Transformation nodes
@@ -4452,6 +4455,21 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
44524455
emitJSDocComment(tag.comment);
44534456
}
44544457

4458+
function emitJSDocImportTypeTag(tag: JSDocImportTypeTag) {
4459+
emitJSDocTagName(tag.tagName);
4460+
4461+
writeSpace();
4462+
emit(tag.importClause);
4463+
4464+
writeSpace();
4465+
emitTokenWithComment(SyntaxKind.FromKeyword, tag.importClause.end, writeKeyword, tag);
4466+
4467+
writeSpace();
4468+
emitExpression(tag.moduleSpecifier);
4469+
4470+
emitJSDocComment(tag.comment);
4471+
}
4472+
44554473
function emitJSDocNameReference(node: JSDocNameReference) {
44564474
writeSpace();
44574475
writePunctuation("{");

src/compiler/factory/nodeFactory.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ import {
238238
JSDocEnumTag,
239239
JSDocFunctionType,
240240
JSDocImplementsTag,
241+
JSDocImportTypeTag,
241242
JSDocLink,
242243
JSDocLinkCode,
243244
JSDocLinkPlain,
@@ -882,6 +883,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
882883
updateJSDocImplementsTag,
883884
createJSDocSeeTag,
884885
updateJSDocSeeTag,
886+
createJSDocImportTypeTag,
887+
updateJSDocImportTypeTag,
885888
createJSDocNameReference,
886889
updateJSDocNameReference,
887890
createJSDocMemberName,
@@ -5554,6 +5557,24 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
55545557
: node;
55555558
}
55565559

5560+
// @api
5561+
function createJSDocImportTypeTag(tagName: Identifier | undefined, importClause: ImportClause, moduleSpecifier: Expression, comment?: string | NodeArray<JSDocComment>): JSDocImportTypeTag {
5562+
const node = createBaseJSDocTag<JSDocImportTypeTag>(SyntaxKind.JSDocImportTypeTag, tagName ?? createIdentifier("importType"), comment);
5563+
node.importClause = importClause;
5564+
node.moduleSpecifier = moduleSpecifier;
5565+
node.comment = comment;
5566+
return node;
5567+
}
5568+
5569+
function updateJSDocImportTypeTag(node: JSDocImportTypeTag, tagName: Identifier | undefined, importClause: ImportClause, moduleSpecifier: Expression, comment: string | NodeArray<JSDocComment> | undefined): JSDocImportTypeTag {
5570+
return node.tagName !== tagName
5571+
|| node.comment !== comment
5572+
|| node.importClause !== importClause
5573+
|| node.moduleSpecifier !== moduleSpecifier
5574+
? update(createJSDocImportTypeTag(tagName, importClause, moduleSpecifier, comment), node)
5575+
: node;
5576+
}
5577+
55575578
// @api
55585579
function createJSDocText(text: string): JSDocText {
55595580
const node = createBaseNode<JSDocText>(SyntaxKind.JSDocText);
@@ -7265,6 +7286,8 @@ function getDefaultTagNameForKind(kind: JSDocTag["kind"]): string {
72657286
return "augments";
72667287
case SyntaxKind.JSDocImplementsTag:
72677288
return "implements";
7289+
case SyntaxKind.JSDocImportTypeTag:
7290+
return "importType";
72687291
default:
72697292
return Debug.fail(`Unsupported kind: ${Debug.formatSyntaxKind(kind)}`);
72707293
}

src/compiler/factory/nodeTests.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ import {
9191
JSDocEnumTag,
9292
JSDocFunctionType,
9393
JSDocImplementsTag,
94+
JSDocImportTypeTag,
9495
JSDocLink,
9596
JSDocLinkCode,
9697
JSDocLinkPlain,
@@ -1202,6 +1203,10 @@ export function isJSDocThrowsTag(node: Node): node is JSDocThrowsTag {
12021203
return node.kind === SyntaxKind.JSDocThrowsTag;
12031204
}
12041205

1206+
export function isJSDocImportTypeTag(node: Node): node is JSDocImportTypeTag {
1207+
return node.kind === SyntaxKind.JSDocImportTypeTag;
1208+
}
1209+
12051210
// Synthesized list
12061211

12071212
/** @internal */

src/compiler/parser.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ import {
175175
JSDocEnumTag,
176176
JSDocFunctionType,
177177
JSDocImplementsTag,
178+
JSDocImportTypeTag,
178179
JSDocLink,
179180
JSDocLinkCode,
180181
JSDocLinkPlain,
@@ -1125,6 +1126,7 @@ const forEachChildTable: ForEachChildTable = {
11251126
[SyntaxKind.JSDocReadonlyTag]: forEachChildInJSDocTag,
11261127
[SyntaxKind.JSDocDeprecatedTag]: forEachChildInJSDocTag,
11271128
[SyntaxKind.JSDocOverrideTag]: forEachChildInJSDocTag,
1129+
[SyntaxKind.JSDocImportTypeTag]: forEachChildInJSDocImportTypeTag,
11281130
[SyntaxKind.PartiallyEmittedExpression]: forEachChildInPartiallyEmittedExpression,
11291131
};
11301132

@@ -1214,6 +1216,13 @@ function forEachChildInJSDocTag<T>(node: JSDocUnknownTag | JSDocClassTag | JSDoc
12141216
|| (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment));
12151217
}
12161218

1219+
function forEachChildInJSDocImportTypeTag<T>(node: JSDocImportTypeTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
1220+
return visitNode(cbNode, node.tagName)
1221+
|| visitNode(cbNode, node.importClause)
1222+
|| visitNode(cbNode, node.moduleSpecifier)
1223+
|| (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment));
1224+
}
1225+
12171226
function forEachChildInPartiallyEmittedExpression<T>(node: PartiallyEmittedExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
12181227
return visitNode(cbNode, node.expression);
12191228
}
@@ -9073,6 +9082,9 @@ namespace Parser {
90739082
case "throws":
90749083
tag = parseThrowsTag(start, tagName, margin, indentText);
90759084
break;
9085+
case "importType":
9086+
tag = parseImportTypeTag(start, tagName, margin, indentText);
9087+
break;
90769088
default:
90779089
tag = parseUnknownTag(start, tagName, margin, indentText);
90789090
break;
@@ -9445,6 +9457,31 @@ namespace Parser {
94459457
return finishNode(factory.createJSDocSatisfiesTag(tagName, typeExpression, comments), start);
94469458
}
94479459

9460+
function parseImportTypeTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocImportTypeTag {
9461+
const afterImportTypeTagPos = scanner.getTokenEnd();
9462+
9463+
let identifier: Identifier | undefined;
9464+
if (isIdentifier()) {
9465+
identifier = parseIdentifier();
9466+
}
9467+
9468+
let importClause: ImportClause | undefined;
9469+
if (
9470+
identifier // @importType id
9471+
|| token() === SyntaxKind.AsteriskToken // @importType *
9472+
|| token() === SyntaxKind.OpenBraceToken // @importType {
9473+
) {
9474+
importClause = parseImportClause(identifier, afterImportTypeTagPos, /*isTypeOnly*/ true);
9475+
parseExpected(SyntaxKind.FromKeyword);
9476+
}
9477+
9478+
Debug.assert(importClause);
9479+
9480+
const moduleSpecifier = parseModuleSpecifier();
9481+
const comments = margin !== undefined && indentText !== undefined ? parseTrailingTagComments(start, getNodePos(), margin, indentText) : undefined;
9482+
return finishNode(factory.createJSDocImportTypeTag(tagName, importClause, moduleSpecifier, comments), start);
9483+
}
9484+
94489485
function parseExpressionWithTypeArgumentsForAugments(): ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression; } {
94499486
const usedBrace = parseOptional(SyntaxKind.OpenBraceToken);
94509487
const pos = getNodePos();

src/compiler/program.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ import {
195195
isImportTypeNode,
196196
isIncrementalCompilation,
197197
isInJSFile,
198+
isJSDocImportTypeTag,
198199
isLiteralImportTypeNode,
199200
isModifier,
200201
isModuleDeclaration,
@@ -3370,6 +3371,10 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
33703371
collectDynamicImportOrRequireCalls(file);
33713372
}
33723373

3374+
if (isJavaScriptFile) {
3375+
collectJsDocImportTypeReferences(file);
3376+
}
3377+
33733378
file.imports = imports || emptyArray;
33743379
file.moduleAugmentations = moduleAugmentations || emptyArray;
33753380
file.ambientModuleNames = ambientModules || emptyArray;
@@ -3444,6 +3449,20 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
34443449
}
34453450
}
34463451

3452+
function collectJsDocImportTypeReferences(file: SourceFile) {
3453+
const r = /@importType/g;
3454+
while (r.exec(file.text) !== null) { // eslint-disable-line no-null/no-null
3455+
const node = getNodeAtPosition(file, r.lastIndex);
3456+
if (isJSDocImportTypeTag(node)) {
3457+
const moduleNameExpr = getExternalModuleName(node);
3458+
if (moduleNameExpr && isStringLiteral(moduleNameExpr) && moduleNameExpr.text) {
3459+
setParentRecursive(node, /*incremental*/ false);
3460+
imports = append(imports, moduleNameExpr);
3461+
}
3462+
}
3463+
}
3464+
}
3465+
34473466
/** Returns a token if position is in [start-of-leading-trivia, end), includes JSDoc only in JS files */
34483467
function getNodeAtPosition(sourceFile: SourceFile, position: number): Node {
34493468
let current: Node = sourceFile;

src/compiler/transformers/declarations.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ import {
118118
isIndexSignatureDeclaration,
119119
isInterfaceDeclaration,
120120
isInternalDeclaration,
121+
isJSDocImportTypeTag,
121122
isJsonSourceFile,
122123
isLateVisibilityPaintedStatement,
123124
isLiteralImportTypeNode,
@@ -1458,6 +1459,8 @@ export function transformDeclarations(context: TransformationContext) {
14581459
}
14591460
if (isDeclaration(input) && isDeclarationAndNotVisible(input)) return;
14601461

1462+
if (isJSDocImportTypeTag(input)) return;
1463+
14611464
// Elide implementation signatures from overload sets
14621465
if (isFunctionLike(input) && resolver.isImplementationOfOverload(input)) return;
14631466

0 commit comments

Comments
 (0)