Skip to content

Commit e160bc8

Browse files
Type-only import specifiers (#45998)
* Parse type-only import specifiers * Add type-only export specifiers * Update transform and emit * Update checking * Fix elision when combined with importsNotUsedAsValues=preserve * Accept baselines * Add test * WIP auto imports updates * First auto-imports test working * More auto-import tests * Fix auto imports of type-only exports * Add test for promoting type-only import * Sort import/export specifiers by type-onlyness * Update completions for `import { type |` * Update other completions tests * Respect organize imports sorting when promoting type-only to regular while adding a specifier * Fix comment mistakes * Update src/services/codefixes/importFixes.ts Co-authored-by: Daniel Rosenwasser <[email protected]> * Rearrange some order of assignments in parser * Split huge if statement * Remove redundant check * Update new transformer * Fix import statement completions * Fix type keyword completions good grief * Fix last tests Co-authored-by: Daniel Rosenwasser <[email protected]>
1 parent 26aef89 commit e160bc8

File tree

80 files changed

+1841
-348
lines changed

Some content is hidden

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

80 files changed

+1841
-348
lines changed

src/compiler/checker.ts

+41-22
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,7 @@ namespace ts {
721721
getLocalTypeParametersOfClassOrInterfaceOrTypeAlias,
722722
isDeclarationVisible,
723723
isPropertyAccessible,
724+
getTypeOnlyAliasDeclaration,
724725
};
725726

726727
function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode): Signature | undefined {
@@ -2203,8 +2204,7 @@ namespace ts {
22032204
if (!isValidTypeOnlyAliasUseSite(useSite)) {
22042205
const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(symbol);
22052206
if (typeOnlyDeclaration) {
2206-
const isExport = typeOnlyDeclarationIsExport(typeOnlyDeclaration);
2207-
const message = isExport
2207+
const message = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier
22082208
? Diagnostics._0_cannot_be_used_as_a_value_because_it_was_exported_using_export_type
22092209
: Diagnostics._0_cannot_be_used_as_a_value_because_it_was_imported_using_import_type;
22102210
const unescapedName = unescapeLeadingUnderscores(name);
@@ -2222,7 +2222,7 @@ namespace ts {
22222222
diagnostic,
22232223
createDiagnosticForNode(
22242224
typeOnlyDeclaration,
2225-
typeOnlyDeclarationIsExport(typeOnlyDeclaration) ? Diagnostics._0_was_exported_here : Diagnostics._0_was_imported_here,
2225+
typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier ? Diagnostics._0_was_exported_here : Diagnostics._0_was_imported_here,
22262226
unescapedName));
22272227
}
22282228

@@ -2590,17 +2590,15 @@ namespace ts {
25902590
function checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node: ImportEqualsDeclaration, resolved: Symbol | undefined) {
25912591
if (markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false) && !node.isTypeOnly) {
25922592
const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(getSymbolOfNode(node))!;
2593-
const isExport = typeOnlyDeclarationIsExport(typeOnlyDeclaration);
2593+
const isExport = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier;
25942594
const message = isExport
25952595
? Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_exported_using_export_type
25962596
: Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_imported_using_import_type;
25972597
const relatedMessage = isExport
25982598
? Diagnostics._0_was_exported_here
25992599
: Diagnostics._0_was_imported_here;
26002600

2601-
// Non-null assertion is safe because the optionality comes from ImportClause,
2602-
// but if an ImportClause was the typeOnlyDeclaration, it had to have a `name`.
2603-
const name = unescapeLeadingUnderscores(typeOnlyDeclaration.name!.escapedText);
2601+
const name = unescapeLeadingUnderscores(typeOnlyDeclaration.name.escapedText);
26042602
addRelatedInfo(error(node.moduleReference, message), createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, name));
26052603
}
26062604
}
@@ -3054,13 +3052,13 @@ namespace ts {
30543052
* and issue an error if so.
30553053
*
30563054
* @param aliasDeclaration The alias declaration not marked as type-only
3055+
* @param immediateTarget The symbol to which the alias declaration immediately resolves
3056+
* @param finalTarget The symbol to which the alias declaration ultimately resolves
3057+
* @param overwriteEmpty Checks `resolvesToSymbol` for type-only declarations even if `aliasDeclaration`
30573058
* has already been marked as not resolving to a type-only alias. Used when recursively resolving qualified
30583059
* names of import aliases, e.g. `import C = a.b.C`. If namespace `a` is not found to be type-only, the
30593060
* import declaration will initially be marked as not resolving to a type-only symbol. But, namespace `b`
30603061
* must still be checked for a type-only marker, overwriting the previous negative result if found.
3061-
* @param immediateTarget The symbol to which the alias declaration immediately resolves
3062-
* @param finalTarget The symbol to which the alias declaration ultimately resolves
3063-
* @param overwriteEmpty Checks `resolvesToSymbol` for type-only declarations even if `aliasDeclaration`
30643062
*/
30653063
function markSymbolOfAliasDeclarationIfTypeOnly(
30663064
aliasDeclaration: Declaration | undefined,
@@ -3094,7 +3092,7 @@ namespace ts {
30943092
}
30953093

30963094
/** Indicates that a symbol directly or indirectly resolves to a type-only import or export. */
3097-
function getTypeOnlyAliasDeclaration(symbol: Symbol): TypeOnlyCompatibleAliasDeclaration | undefined {
3095+
function getTypeOnlyAliasDeclaration(symbol: Symbol): TypeOnlyAliasDeclaration | undefined {
30983096
if (!(symbol.flags & SymbolFlags.Alias)) {
30993097
return undefined;
31003098
}
@@ -6536,7 +6534,7 @@ namespace ts {
65366534
/*decorators*/ undefined,
65376535
/*modifiers*/ undefined,
65386536
/*isTypeOnly*/ false,
6539-
factory.createNamedExports(map(flatMap(excessExports, e => getNamesOfDeclaration(e)), id => factory.createExportSpecifier(/*alias*/ undefined, id))),
6537+
factory.createNamedExports(map(flatMap(excessExports, e => getNamesOfDeclaration(e)), id => factory.createExportSpecifier(/*isTypeOnly*/ false, /*alias*/ undefined, id))),
65406538
/*moduleSpecifier*/ undefined
65416539
)])
65426540
)
@@ -6794,7 +6792,7 @@ namespace ts {
67946792
/*decorators*/ undefined,
67956793
/*modifiers*/ undefined,
67966794
/*isTypeOnly*/ false,
6797-
factory.createNamedExports([factory.createExportSpecifier(alias, localName)])
6795+
factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, alias, localName)])
67986796
),
67996797
ModifierFlags.None
68006798
);
@@ -6832,7 +6830,7 @@ namespace ts {
68326830
/*decorators*/ undefined,
68336831
/*modifiers*/ undefined,
68346832
/*isTypeOnly*/ false,
6835-
factory.createNamedExports([factory.createExportSpecifier(name, localName)])
6833+
factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, name, localName)])
68366834
),
68376835
ModifierFlags.None
68386836
);
@@ -6892,7 +6890,7 @@ namespace ts {
68926890
/*decorators*/ undefined,
68936891
/*modifiers*/ undefined,
68946892
/*isTypeOnly*/ false,
6895-
factory.createNamedExports([factory.createExportSpecifier(getInternalSymbolName(symbol, symbolName), symbolName)])
6893+
factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, getInternalSymbolName(symbol, symbolName), symbolName)])
68966894
), ModifierFlags.None);
68976895
}
68986896
}
@@ -7031,7 +7029,7 @@ namespace ts {
70317029
const target = aliasDecl && getTargetOfAliasDeclaration(aliasDecl, /*dontRecursivelyResolve*/ true);
70327030
includePrivateSymbol(target || s);
70337031
const targetName = target ? getInternalSymbolName(target, unescapeLeadingUnderscores(target.escapedName)) : localName;
7034-
return factory.createExportSpecifier(name === targetName ? undefined : targetName, name);
7032+
return factory.createExportSpecifier(/*isTypeOnly*/ false, name === targetName ? undefined : targetName, name);
70357033
}))
70367034
)]);
70377035
addResult(factory.createModuleDeclaration(
@@ -7137,7 +7135,7 @@ namespace ts {
71377135
/*decorators*/ undefined,
71387136
/*modifiers*/ undefined,
71397137
/*isTypeOnly*/ false,
7140-
factory.createNamedExports([factory.createExportSpecifier(d.expression, factory.createIdentifier(InternalSymbolName.Default))])
7138+
factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, d.expression, factory.createIdentifier(InternalSymbolName.Default))])
71417139
) : d);
71427140
const exportModifierStripped = every(defaultReplaced, d => hasSyntacticModifier(d, ModifierFlags.Export)) ? map(defaultReplaced, removeExportModifier) : defaultReplaced;
71437141
fakespace = factory.updateModuleDeclaration(
@@ -7313,6 +7311,7 @@ namespace ts {
73137311
/*decorators*/ undefined,
73147312
/*modifiers*/ undefined,
73157313
factory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, factory.createNamedImports([factory.createImportSpecifier(
7314+
/*isTypeOnly*/ false,
73167315
propertyName && isIdentifier(propertyName) ? factory.createIdentifier(idText(propertyName)) : undefined,
73177316
factory.createIdentifier(localName)
73187317
)])),
@@ -7424,6 +7423,7 @@ namespace ts {
74247423
/*importClause*/ undefined,
74257424
factory.createNamedImports([
74267425
factory.createImportSpecifier(
7426+
/*isTypeOnly*/ false,
74277427
localName !== verbatimTargetName ? factory.createIdentifier(verbatimTargetName) : undefined,
74287428
factory.createIdentifier(localName)
74297429
)
@@ -7470,7 +7470,7 @@ namespace ts {
74707470
/*decorators*/ undefined,
74717471
/*modifiers*/ undefined,
74727472
/*isTypeOnly*/ false,
7473-
factory.createNamedExports([factory.createExportSpecifier(localName !== targetName ? targetName : undefined, localName)]),
7473+
factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, localName !== targetName ? targetName : undefined, localName)]),
74747474
specifier
74757475
), ModifierFlags.None);
74767476
}
@@ -39415,11 +39415,15 @@ namespace ts {
3941539415
}
3941639416

3941739417
function checkGrammarExportDeclaration(node: ExportDeclaration): boolean {
39418-
const isTypeOnlyExportStar = node.isTypeOnly && node.exportClause?.kind !== SyntaxKind.NamedExports;
39419-
if (isTypeOnlyExportStar) {
39420-
grammarErrorOnNode(node, Diagnostics.Only_named_exports_may_use_export_type);
39418+
if (node.isTypeOnly) {
39419+
if (node.exportClause?.kind === SyntaxKind.NamedExports) {
39420+
return checkGrammarNamedImportsOrExports(node.exportClause);
39421+
}
39422+
else {
39423+
return grammarErrorOnNode(node, Diagnostics.Only_named_exports_may_use_export_type);
39424+
}
3942139425
}
39422-
return !isTypeOnlyExportStar;
39426+
return false;
3942339427
}
3942439428

3942539429
function checkGrammarModuleElementContext(node: Statement, errorMessage: DiagnosticMessage): boolean {
@@ -43445,9 +43449,24 @@ namespace ts {
4344543449
if (node.isTypeOnly && node.name && node.namedBindings) {
4344643450
return grammarErrorOnNode(node, Diagnostics.A_type_only_import_can_specify_a_default_import_or_named_bindings_but_not_both);
4344743451
}
43452+
if (node.isTypeOnly && node.namedBindings?.kind === SyntaxKind.NamedImports) {
43453+
return checkGrammarNamedImportsOrExports(node.namedBindings);
43454+
}
4344843455
return false;
4344943456
}
4345043457

43458+
function checkGrammarNamedImportsOrExports(namedBindings: NamedImportsOrExports): boolean {
43459+
return !!forEach<ImportSpecifier | ExportSpecifier, boolean>(namedBindings.elements, specifier => {
43460+
if (specifier.isTypeOnly) {
43461+
return grammarErrorOnFirstToken(
43462+
specifier,
43463+
specifier.kind === SyntaxKind.ImportSpecifier
43464+
? Diagnostics.The_type_modifier_cannot_be_used_on_a_named_import_when_import_type_is_used_on_its_import_statement
43465+
: Diagnostics.The_type_modifier_cannot_be_used_on_a_named_export_when_export_type_is_used_on_its_export_statement);
43466+
}
43467+
});
43468+
}
43469+
4345143470
function checkGrammarImportCallExpression(node: ImportCall): boolean {
4345243471
if (moduleKind === ModuleKind.ES2015) {
4345343472
return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_are_only_supported_when_the_module_flag_is_set_to_es2020_esnext_commonjs_amd_system_umd_node12_or_nodenext);

src/compiler/diagnosticMessages.json

+8
Original file line numberDiff line numberDiff line change
@@ -1434,6 +1434,14 @@
14341434
"code": 2205,
14351435
"elidedInCompatabilityPyramid": true
14361436
},
1437+
"The 'type' modifier cannot be used on a named import when 'import type' is used on its import statement.": {
1438+
"category": "Error",
1439+
"code": 2206
1440+
},
1441+
"The 'type' modifier cannot be used on a named export when 'export type' is used on its export statement.": {
1442+
"category": "Error",
1443+
"code": 2207
1444+
},
14371445

14381446
"Duplicate identifier '{0}'.": {
14391447
"category": "Error",

src/compiler/emitter.ts

+4
Original file line numberDiff line numberDiff line change
@@ -3441,6 +3441,10 @@ namespace ts {
34413441
}
34423442

34433443
function emitImportOrExportSpecifier(node: ImportOrExportSpecifier) {
3444+
if (node.isTypeOnly) {
3445+
writeKeyword("type");
3446+
writeSpace();
3447+
}
34443448
if (node.propertyName) {
34453449
emit(node.propertyName);
34463450
writeSpace();

src/compiler/factory/nodeFactory.ts

+13-9
Original file line numberDiff line numberDiff line change
@@ -4089,8 +4089,9 @@ namespace ts {
40894089
}
40904090

40914091
// @api
4092-
function createImportSpecifier(propertyName: Identifier | undefined, name: Identifier) {
4092+
function createImportSpecifier(isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) {
40934093
const node = createBaseNode<ImportSpecifier>(SyntaxKind.ImportSpecifier);
4094+
node.isTypeOnly = isTypeOnly;
40944095
node.propertyName = propertyName;
40954096
node.name = name;
40964097
node.transformFlags |=
@@ -4101,10 +4102,11 @@ namespace ts {
41014102
}
41024103

41034104
// @api
4104-
function updateImportSpecifier(node: ImportSpecifier, propertyName: Identifier | undefined, name: Identifier) {
4105-
return node.propertyName !== propertyName
4105+
function updateImportSpecifier(node: ImportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) {
4106+
return node.isTypeOnly !== isTypeOnly
4107+
|| node.propertyName !== propertyName
41064108
|| node.name !== name
4107-
? update(createImportSpecifier(propertyName, name), node)
4109+
? update(createImportSpecifier(isTypeOnly, propertyName, name), node)
41084110
: node;
41094111
}
41104112

@@ -4205,8 +4207,9 @@ namespace ts {
42054207
}
42064208

42074209
// @api
4208-
function createExportSpecifier(propertyName: string | Identifier | undefined, name: string | Identifier) {
4210+
function createExportSpecifier(isTypeOnly: boolean, propertyName: string | Identifier | undefined, name: string | Identifier) {
42094211
const node = createBaseNode<ExportSpecifier>(SyntaxKind.ExportSpecifier);
4212+
node.isTypeOnly = isTypeOnly;
42104213
node.propertyName = asName(propertyName);
42114214
node.name = asName(name);
42124215
node.transformFlags |=
@@ -4217,10 +4220,11 @@ namespace ts {
42174220
}
42184221

42194222
// @api
4220-
function updateExportSpecifier(node: ExportSpecifier, propertyName: Identifier | undefined, name: Identifier) {
4221-
return node.propertyName !== propertyName
4223+
function updateExportSpecifier(node: ExportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) {
4224+
return node.isTypeOnly !== isTypeOnly
4225+
|| node.propertyName !== propertyName
42224226
|| node.name !== name
4223-
? update(createExportSpecifier(propertyName, name), node)
4227+
? update(createExportSpecifier(isTypeOnly, propertyName, name), node)
42244228
: node;
42254229
}
42264230

@@ -5488,7 +5492,7 @@ namespace ts {
54885492
/*modifiers*/ undefined,
54895493
/*isTypeOnly*/ false,
54905494
createNamedExports([
5491-
createExportSpecifier(/*propertyName*/ undefined, exportName)
5495+
createExportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, exportName)
54925496
])
54935497
);
54945498
}

src/compiler/factory/utilities.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -500,8 +500,8 @@ namespace ts {
500500
// NOTE: We don't need to care about global import collisions as this is a module.
501501
namedBindings = nodeFactory.createNamedImports(
502502
map(helperNames, name => isFileLevelUniqueName(sourceFile, name)
503-
? nodeFactory.createImportSpecifier(/*propertyName*/ undefined, nodeFactory.createIdentifier(name))
504-
: nodeFactory.createImportSpecifier(nodeFactory.createIdentifier(name), helperFactory.getUnscopedHelperName(name))
503+
? nodeFactory.createImportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, nodeFactory.createIdentifier(name))
504+
: nodeFactory.createImportSpecifier(/*isTypeOnly*/ false, nodeFactory.createIdentifier(name), helperFactory.getUnscopedHelperName(name))
505505
)
506506
);
507507
const parseNode = getOriginalNode(sourceFile, isSourceFile);

0 commit comments

Comments
 (0)