Skip to content

Commit 2de69b0

Browse files
authored
feat(41825): JSDoc equivalent of import * (#57207)
1 parent a22aaf0 commit 2de69b0

File tree

116 files changed

+2689
-91
lines changed

Some content is hidden

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

116 files changed

+2689
-91
lines changed

Diff for: src/compiler/binder.ts

+47
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ import {
9494
getExpandoInitializer,
9595
getHostSignatureFromJSDoc,
9696
getImmediatelyInvokedFunctionExpression,
97+
getJSDocHost,
9798
getJSDocTypeTag,
9899
getLeftmostAccessExpression,
99100
getNameOfDeclaration,
@@ -229,6 +230,7 @@ import {
229230
JSDocClassTag,
230231
JSDocEnumTag,
231232
JSDocFunctionType,
233+
JSDocImportTag,
232234
JSDocOverloadTag,
233235
JSDocParameterTag,
234236
JSDocPropertyLikeTag,
@@ -525,6 +527,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
525527
var lastContainer: HasLocals;
526528
var delayedTypeAliases: (JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag)[];
527529
var seenThisKeyword: boolean;
530+
var jsDocImports: JSDocImportTag[];
528531

529532
// state used by control flow analysis
530533
var currentFlow: FlowNode;
@@ -592,6 +595,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
592595
file.symbolCount = symbolCount;
593596
file.classifiableNames = classifiableNames;
594597
delayedBindJSDocTypedefTag();
598+
bindJSDocImports();
595599
}
596600

597601
file = undefined!;
@@ -603,6 +607,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
603607
blockScopeContainer = undefined!;
604608
lastContainer = undefined!;
605609
delayedTypeAliases = undefined!;
610+
jsDocImports = undefined!;
606611
seenThisKeyword = false;
607612
currentFlow = undefined!;
608613
currentBreakTarget = undefined;
@@ -1181,6 +1186,9 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
11811186
bindJSDocTypeAlias(node as JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag);
11821187
break;
11831188
// In source files and blocks, bind functions first to match hoisting that occurs at runtime
1189+
case SyntaxKind.JSDocImportTag:
1190+
bindJSDocImportTag(node as JSDocImportTag);
1191+
break;
11841192
case SyntaxKind.SourceFile: {
11851193
bindEachFunctionsFirst((node as SourceFile).statements);
11861194
bind((node as SourceFile).endOfFileToken);
@@ -2065,6 +2073,14 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
20652073
}
20662074
}
20672075

2076+
function bindJSDocImportTag(node: JSDocImportTag) {
2077+
bind(node.tagName);
2078+
2079+
if (typeof node.comment !== "string") {
2080+
bindEach(node.comment);
2081+
}
2082+
}
2083+
20682084
function bindOptionalExpression(node: Expression, trueTarget: FlowLabel, falseTarget: FlowLabel) {
20692085
doWithConditionalBranches(bind, node, trueTarget, falseTarget);
20702086
if (!isOptionalChain(node) || isOutermostOptionalChain(node)) {
@@ -2443,6 +2459,35 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
24432459
currentFlow = saveCurrentFlow;
24442460
}
24452461

2462+
function bindJSDocImports() {
2463+
if (jsDocImports === undefined) {
2464+
return;
2465+
}
2466+
2467+
const saveContainer = container;
2468+
const saveLastContainer = lastContainer;
2469+
const saveBlockScopeContainer = blockScopeContainer;
2470+
const saveParent = parent;
2471+
const saveCurrentFlow = currentFlow;
2472+
2473+
for (const jsDocImportTag of jsDocImports) {
2474+
const host = getJSDocHost(jsDocImportTag);
2475+
const enclosingContainer = host ? getEnclosingContainer(host) as IsContainer | undefined : undefined;
2476+
const enclosingBlockScopeContainer = host ? getEnclosingBlockScopeContainer(host) as IsBlockScopedContainer | undefined : undefined;
2477+
container = enclosingContainer || file;
2478+
blockScopeContainer = enclosingBlockScopeContainer || file;
2479+
currentFlow = initFlowNode({ flags: FlowFlags.Start });
2480+
parent = jsDocImportTag;
2481+
bind(jsDocImportTag.importClause);
2482+
}
2483+
2484+
container = saveContainer;
2485+
lastContainer = saveLastContainer;
2486+
blockScopeContainer = saveBlockScopeContainer;
2487+
parent = saveParent;
2488+
currentFlow = saveCurrentFlow;
2489+
}
2490+
24462491
// The binder visits every node in the syntax tree so it is a convenient place to perform a single localized
24472492
// check for reserved words used as identifiers in strict mode code, as well as `yield` or `await` in
24482493
// [Yield] or [Await] contexts, respectively.
@@ -2995,6 +3040,8 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
29953040
return (delayedTypeAliases || (delayedTypeAliases = [])).push(node as JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag);
29963041
case SyntaxKind.JSDocOverloadTag:
29973042
return bind((node as JSDocOverloadTag).typeExpression);
3043+
case SyntaxKind.JSDocImportTag:
3044+
return (jsDocImports || (jsDocImports = [])).push(node as JSDocImportTag);
29983045
}
29993046
}
30003047

Diff for: src/compiler/checker.ts

+26-10
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import {
1111
AmbientModuleDeclaration,
1212
and,
1313
AnonymousType,
14+
AnyImportOrJsDocImport,
1415
AnyImportOrReExport,
15-
AnyImportSyntax,
1616
append,
1717
appendIfUnique,
1818
ArrayBindingPattern,
@@ -581,6 +581,7 @@ import {
581581
isJSDocCallbackTag,
582582
isJSDocConstructSignature,
583583
isJSDocFunctionType,
584+
isJSDocImportTag,
584585
isJSDocIndexSignature,
585586
isJSDocLinkLike,
586587
isJSDocMemberName,
@@ -770,6 +771,7 @@ import {
770771
JSDocEnumTag,
771772
JSDocFunctionType,
772773
JSDocImplementsTag,
774+
JSDocImportTag,
773775
JSDocLink,
774776
JSDocLinkCode,
775777
JSDocLinkPlain,
@@ -3382,6 +3384,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
33823384
case SyntaxKind.JSDocTypedefTag:
33833385
case SyntaxKind.JSDocCallbackTag:
33843386
case SyntaxKind.JSDocEnumTag:
3387+
case SyntaxKind.JSDocImportTag:
33853388
// js type aliases do not resolve names from their host, so skip past it
33863389
const root = getJSDocRoot(location);
33873390
if (root) {
@@ -3950,7 +3953,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
39503953
|| (n === stopAt || isFunctionLike(n) && (!getImmediatelyInvokedFunctionExpression(n) || (getFunctionFlags(n) & FunctionFlags.AsyncGenerator)) ? "quit" : false));
39513954
}
39523955

3953-
function getAnyImportSyntax(node: Node): AnyImportSyntax | undefined {
3956+
function getAnyImportSyntax(node: Node): AnyImportOrJsDocImport | undefined {
39543957
switch (node.kind) {
39553958
case SyntaxKind.ImportEqualsDeclaration:
39563959
return node as ImportEqualsDeclaration;
@@ -4288,8 +4291,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
42884291
}
42894292
}
42904293

4291-
function getExternalModuleMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration, specifier: ImportOrExportSpecifier | BindingElement | PropertyAccessExpression, dontResolveAlias = false): Symbol | undefined {
4292-
const moduleSpecifier = getExternalModuleRequireArgument(node) || (node as ImportDeclaration | ExportDeclaration).moduleSpecifier!;
4294+
function getExternalModuleMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration | JSDocImportTag, specifier: ImportOrExportSpecifier | BindingElement | PropertyAccessExpression, dontResolveAlias = false): Symbol | undefined {
4295+
const moduleSpecifier = getExternalModuleRequireArgument(node) || (node as ImportDeclaration | ExportDeclaration | JSDocImportTag).moduleSpecifier!;
42934296
const moduleSymbol = resolveExternalModuleName(node, moduleSpecifier)!; // TODO: GH#18217
42944297
const name = !isPropertyAccessExpression(specifier) && specifier.propertyName || specifier.name;
42954298
if (!isIdentifier(name)) {
@@ -5014,6 +5017,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
50145017
? location
50155018
: (isModuleDeclaration(location) ? location : location.parent && isModuleDeclaration(location.parent) && location.parent.name === location ? location.parent : undefined)?.name ||
50165019
(isLiteralImportTypeNode(location) ? location : undefined)?.argument.literal ||
5020+
(isInJSFile(location) && isJSDocImportTag(location) ? location.moduleSpecifier : undefined) ||
50175021
(isVariableDeclaration(location) && location.initializer && isRequireCall(location.initializer, /*requireStringLiteralLikeArgument*/ true) ? location.initializer.arguments[0] : undefined) ||
50185022
findAncestor(location, isImportCall)?.arguments[0] ||
50195023
findAncestor(location, isImportDeclaration)?.moduleSpecifier ||
@@ -9800,12 +9804,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
98009804
case SyntaxKind.ImportClause: {
98019805
const generatedSpecifier = getSpecifierForModuleSymbol(target.parent || target, context); // generate specifier (even though we're reusing and existing one) for ambient module reference include side effects
98029806
const specifier = bundled ? factory.createStringLiteral(generatedSpecifier) : (node as ImportClause).parent.moduleSpecifier;
9807+
const attributes = isImportDeclaration(node.parent) ? node.parent.attributes : undefined;
9808+
const isTypeOnly = isJSDocImportTag((node as ImportClause).parent);
98039809
addResult(
98049810
factory.createImportDeclaration(
98059811
/*modifiers*/ undefined,
9806-
factory.createImportClause(/*isTypeOnly*/ false, factory.createIdentifier(localName), /*namedBindings*/ undefined),
9812+
factory.createImportClause(isTypeOnly, factory.createIdentifier(localName), /*namedBindings*/ undefined),
98079813
specifier,
9808-
(node as ImportClause).parent.attributes,
9814+
attributes,
98099815
),
98109816
ModifierFlags.None,
98119817
);
@@ -9814,10 +9820,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
98149820
case SyntaxKind.NamespaceImport: {
98159821
const generatedSpecifier = getSpecifierForModuleSymbol(target.parent || target, context); // generate specifier (even though we're reusing and existing one) for ambient module reference include side effects
98169822
const specifier = bundled ? factory.createStringLiteral(generatedSpecifier) : (node as NamespaceImport).parent.parent.moduleSpecifier;
9823+
const isTypeOnly = isJSDocImportTag((node as NamespaceImport).parent.parent);
98179824
addResult(
98189825
factory.createImportDeclaration(
98199826
/*modifiers*/ undefined,
9820-
factory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, factory.createNamespaceImport(factory.createIdentifier(localName))),
9827+
factory.createImportClause(isTypeOnly, /*name*/ undefined, factory.createNamespaceImport(factory.createIdentifier(localName))),
98219828
specifier,
98229829
(node as ImportClause).parent.attributes,
98239830
),
@@ -9839,11 +9846,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
98399846
case SyntaxKind.ImportSpecifier: {
98409847
const generatedSpecifier = getSpecifierForModuleSymbol(target.parent || target, context); // generate specifier (even though we're reusing and existing one) for ambient module reference include side effects
98419848
const specifier = bundled ? factory.createStringLiteral(generatedSpecifier) : (node as ImportSpecifier).parent.parent.parent.moduleSpecifier;
9849+
const isTypeOnly = isJSDocImportTag((node as ImportSpecifier).parent.parent.parent);
98429850
addResult(
98439851
factory.createImportDeclaration(
98449852
/*modifiers*/ undefined,
98459853
factory.createImportClause(
9846-
/*isTypeOnly*/ false,
9854+
isTypeOnly,
98479855
/*name*/ undefined,
98489856
factory.createNamedImports([
98499857
factory.createImportSpecifier(
@@ -42193,6 +42201,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4219342201
}
4219442202
}
4219542203

42204+
function checkJSDocImportTag(node: JSDocImportTag) {
42205+
checkImportAttributes(node);
42206+
}
42207+
4219642208
function checkJSDocImplementsTag(node: JSDocImplementsTag): void {
4219742209
const classLike = getEffectiveJSDocHost(node);
4219842210
if (!classLike || !isClassDeclaration(classLike) && !isClassExpression(classLike)) {
@@ -46402,7 +46414,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4640246414
}
4640346415
}
4640446416

46405-
function checkImportAttributes(declaration: ImportDeclaration | ExportDeclaration) {
46417+
function checkImportAttributes(declaration: ImportDeclaration | ExportDeclaration | JSDocImportTag) {
4640646418
const node = declaration.attributes;
4640746419
if (node) {
4640846420
const importAttributesType = getGlobalImportAttributesType(/*reportErrors*/ true);
@@ -46429,7 +46441,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4642946441
return grammarErrorOnNode(node, message);
4643046442
}
4643146443

46432-
if (isImportDeclaration(declaration) ? declaration.importClause?.isTypeOnly : declaration.isTypeOnly) {
46444+
const isTypeOnly = isJSDocImportTag(declaration) || (isImportDeclaration(declaration) ? declaration.importClause?.isTypeOnly : declaration.isTypeOnly);
46445+
if (isTypeOnly) {
4643346446
return grammarErrorOnNode(node, isImportAttributes ? Diagnostics.Import_attributes_cannot_be_used_with_type_only_imports_or_exports : Diagnostics.Import_assertions_cannot_be_used_with_type_only_imports_or_exports);
4643446447
}
4643546448

@@ -46957,6 +46970,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4695746970
return checkJSDocSatisfiesTag(node as JSDocSatisfiesTag);
4695846971
case SyntaxKind.JSDocThisTag:
4695946972
return checkJSDocThisTag(node as JSDocThisTag);
46973+
case SyntaxKind.JSDocImportTag:
46974+
return checkJSDocImportTag(node as JSDocImportTag);
4696046975
case SyntaxKind.IndexedAccessType:
4696146976
return checkIndexedAccessType(node as IndexedAccessTypeNode);
4696246977
case SyntaxKind.MappedType:
@@ -47913,6 +47928,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4791347928
if (
4791447929
(isExternalModuleImportEqualsDeclaration(node.parent.parent) && getExternalModuleImportEqualsDeclarationExpression(node.parent.parent) === node) ||
4791547930
((node.parent.kind === SyntaxKind.ImportDeclaration || node.parent.kind === SyntaxKind.ExportDeclaration) && (node.parent as ImportDeclaration).moduleSpecifier === node) ||
47931+
(isInJSFile(node) && isJSDocImportTag(node.parent) && node.parent.moduleSpecifier === node) ||
4791647932
((isInJSFile(node) && isRequireCall(node.parent, /*requireStringLiteralLikeArgument*/ false)) || isImportCall(node.parent)) ||
4791747933
(isLiteralTypeNode(node.parent) && isLiteralImportTypeNode(node.parent.parent) && node.parent.parent.argument === node.parent)
4791847934
) {

Diff for: src/compiler/emitter.ts

+22
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ import {
243243
JSDocEnumTag,
244244
JSDocFunctionType,
245245
JSDocImplementsTag,
246+
JSDocImportTag,
246247
JSDocNameReference,
247248
JSDocNonNullableType,
248249
JSDocNullableType,
@@ -1826,6 +1827,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
18261827
return emitJSDocTypedefTag(node as JSDocTypedefTag);
18271828
case SyntaxKind.JSDocSeeTag:
18281829
return emitJSDocSeeTag(node as JSDocSeeTag);
1830+
case SyntaxKind.JSDocImportTag:
1831+
return emitJSDocImportTag(node as JSDocImportTag);
18291832
// SyntaxKind.JSDocPropertyTag (see JSDocParameterTag, above)
18301833

18311834
// Transformation nodes
@@ -4006,6 +4009,25 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
40064009
emitJSDocComment(tag.comment);
40074010
}
40084011

4012+
function emitJSDocImportTag(tag: JSDocImportTag) {
4013+
emitJSDocTagName(tag.tagName);
4014+
writeSpace();
4015+
4016+
if (tag.importClause) {
4017+
emit(tag.importClause);
4018+
writeSpace();
4019+
4020+
emitTokenWithComment(SyntaxKind.FromKeyword, tag.importClause.end, writeKeyword, tag);
4021+
writeSpace();
4022+
}
4023+
4024+
emitExpression(tag.moduleSpecifier);
4025+
if (tag.attributes) {
4026+
emitWithLeadingSpace(tag.attributes);
4027+
}
4028+
emitJSDocComment(tag.comment);
4029+
}
4030+
40094031
function emitJSDocNameReference(node: JSDocNameReference) {
40104032
writeSpace();
40114033
writePunctuation("{");

Diff for: src/compiler/factory/nodeFactory.ts

+25
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ import {
226226
JSDocEnumTag,
227227
JSDocFunctionType,
228228
JSDocImplementsTag,
229+
JSDocImportTag,
229230
JSDocLink,
230231
JSDocLinkCode,
231232
JSDocLinkPlain,
@@ -857,6 +858,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
857858
updateJSDocImplementsTag,
858859
createJSDocSeeTag,
859860
updateJSDocSeeTag,
861+
createJSDocImportTag,
862+
updateJSDocImportTag,
860863
createJSDocNameReference,
861864
updateJSDocNameReference,
862865
createJSDocMemberName,
@@ -5523,6 +5526,26 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
55235526
: node;
55245527
}
55255528

5529+
// @api
5530+
function createJSDocImportTag(tagName: Identifier | undefined, importClause: ImportClause | undefined, moduleSpecifier: Expression, attributes?: ImportAttributes, comment?: string | NodeArray<JSDocComment>): JSDocImportTag {
5531+
const node = createBaseJSDocTag<JSDocImportTag>(SyntaxKind.JSDocImportTag, tagName ?? createIdentifier("import"), comment);
5532+
node.importClause = importClause;
5533+
node.moduleSpecifier = moduleSpecifier;
5534+
node.attributes = attributes;
5535+
node.comment = comment;
5536+
return node;
5537+
}
5538+
5539+
function updateJSDocImportTag(node: JSDocImportTag, tagName: Identifier | undefined, importClause: ImportClause | undefined, moduleSpecifier: Expression, attributes: ImportAttributes | undefined, comment: string | NodeArray<JSDocComment> | undefined): JSDocImportTag {
5540+
return node.tagName !== tagName
5541+
|| node.comment !== comment
5542+
|| node.importClause !== importClause
5543+
|| node.moduleSpecifier !== moduleSpecifier
5544+
|| node.attributes !== attributes
5545+
? update(createJSDocImportTag(tagName, importClause, moduleSpecifier, attributes, comment), node)
5546+
: node;
5547+
}
5548+
55265549
// @api
55275550
function createJSDocText(text: string): JSDocText {
55285551
const node = createBaseNode<JSDocText>(SyntaxKind.JSDocText);
@@ -7179,6 +7202,8 @@ function getDefaultTagNameForKind(kind: JSDocTag["kind"]): string {
71797202
return "augments";
71807203
case SyntaxKind.JSDocImplementsTag:
71817204
return "implements";
7205+
case SyntaxKind.JSDocImportTag:
7206+
return "import";
71827207
default:
71837208
return Debug.fail(`Unsupported kind: ${Debug.formatSyntaxKind(kind)}`);
71847209
}

Diff for: src/compiler/factory/nodeTests.ts

+5
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ import {
9191
JSDocEnumTag,
9292
JSDocFunctionType,
9393
JSDocImplementsTag,
94+
JSDocImportTag,
9495
JSDocLink,
9596
JSDocLinkCode,
9697
JSDocLinkPlain,
@@ -1183,6 +1184,10 @@ export function isJSDocThrowsTag(node: Node): node is JSDocThrowsTag {
11831184
return node.kind === SyntaxKind.JSDocThrowsTag;
11841185
}
11851186

1187+
export function isJSDocImportTag(node: Node): node is JSDocImportTag {
1188+
return node.kind === SyntaxKind.JSDocImportTag;
1189+
}
1190+
11861191
// Synthesized list
11871192

11881193
/** @internal */

0 commit comments

Comments
 (0)