Skip to content

Commit 69bc3f3

Browse files
authored
Allow type-only imports on ImportEqualsDeclarations (#41573)
* Allow type-only ImportEqualsDeclarations * Suppress CJS-in-ESM error when type-only * Add grammar error on import type in import alias * Update API baselines * Fix importsNotUsedAsValues with ImportEqualsDeclarations * Make bad error talk words more good for Daniel. Fixes #41603 * One more error message baseline update * Update transformer and emitter
1 parent 8d952cb commit 69bc3f3

36 files changed

+463
-85
lines changed

src/compiler/checker.ts

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2482,7 +2482,7 @@ namespace ts {
24822482
const immediate = resolveExternalModuleName(
24832483
node,
24842484
getExternalModuleRequireArgument(node) || getExternalModuleImportEqualsDeclarationExpression(node));
2485-
const resolved = resolveExternalModuleSymbol(immediate);
2485+
const resolved = resolveExternalModuleSymbol(immediate);
24862486
markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false);
24872487
return resolved;
24882488
}
@@ -2492,7 +2492,7 @@ namespace ts {
24922492
}
24932493

24942494
function checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node: ImportEqualsDeclaration, resolved: Symbol | undefined) {
2495-
if (markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false)) {
2495+
if (markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false) && !node.isTypeOnly) {
24962496
const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(getSymbolOfNode(node))!;
24972497
const isExport = typeOnlyDeclarationIsExport(typeOnlyDeclaration);
24982498
const message = isExport
@@ -6841,13 +6841,15 @@ namespace ts {
68416841
addResult(factory.createImportEqualsDeclaration(
68426842
/*decorators*/ undefined,
68436843
/*modifiers*/ undefined,
6844+
/*isTypeOnly*/ false,
68446845
uniqueName,
68456846
factory.createExternalModuleReference(factory.createStringLiteral(specifier))
68466847
), ModifierFlags.None);
68476848
// import x = _x.z
68486849
addResult(factory.createImportEqualsDeclaration(
68496850
/*decorators*/ undefined,
68506851
/*modifiers*/ undefined,
6852+
/*isTypeOnly*/ false,
68516853
factory.createIdentifier(localName),
68526854
factory.createQualifiedName(uniqueName, initializer.name as Identifier),
68536855
), modifierFlags);
@@ -6867,6 +6869,7 @@ namespace ts {
68676869
addResult(factory.createImportEqualsDeclaration(
68686870
/*decorators*/ undefined,
68696871
/*modifiers*/ undefined,
6872+
/*isTypeOnly*/ false,
68706873
factory.createIdentifier(localName),
68716874
isLocalImport
68726875
? symbolToName(target, context, SymbolFlags.All, /*expectsIdentifier*/ false)
@@ -7024,6 +7027,7 @@ namespace ts {
70247027
addResult(factory.createImportEqualsDeclaration(
70257028
/*decorators*/ undefined,
70267029
/*modifiers*/ undefined,
7030+
/*isTypeOnly*/ false,
70277031
factory.createIdentifier(varName),
70287032
symbolToName(target, context, SymbolFlags.All, /*expectsIdentifier*/ false)
70297033
), ModifierFlags.None);
@@ -36459,9 +36463,12 @@ namespace ts {
3645936463
checkTypeNameIsReserved(node.name, Diagnostics.Import_name_cannot_be_0);
3646036464
}
3646136465
}
36466+
if (node.isTypeOnly) {
36467+
grammarErrorOnNode(node, Diagnostics.An_import_alias_cannot_use_import_type);
36468+
}
3646236469
}
3646336470
else {
36464-
if (moduleKind >= ModuleKind.ES2015 && !(node.flags & NodeFlags.Ambient)) {
36471+
if (moduleKind >= ModuleKind.ES2015 && !node.isTypeOnly && !(node.flags & NodeFlags.Ambient)) {
3646536472
// Import equals declaration is deprecated in es6 or above
3646636473
grammarErrorOnNode(node, Diagnostics.Import_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_import_Asterisk_as_ns_from_mod_import_a_from_mod_import_d_from_mod_or_another_module_format_instead);
3646736474
}
@@ -36552,19 +36559,30 @@ namespace ts {
3655236559
});
3655336560
}
3655436561

36562+
function canConvertImportDeclarationToTypeOnly(statement: Statement) {
36563+
return isImportDeclaration(statement) &&
36564+
statement.importClause &&
36565+
!statement.importClause.isTypeOnly &&
36566+
importClauseContainsReferencedImport(statement.importClause) &&
36567+
!isReferencedAliasDeclaration(statement.importClause, /*checkChildren*/ true) &&
36568+
!importClauseContainsConstEnumUsedAsValue(statement.importClause);
36569+
}
36570+
36571+
function canConvertImportEqualsDeclarationToTypeOnly(statement: Statement) {
36572+
return isImportEqualsDeclaration(statement) &&
36573+
isExternalModuleReference(statement.moduleReference) &&
36574+
!statement.isTypeOnly &&
36575+
getSymbolOfNode(statement).isReferenced &&
36576+
!isReferencedAliasDeclaration(statement, /*checkChildren*/ false) &&
36577+
!getSymbolLinks(getSymbolOfNode(statement)).constEnumReferenced;
36578+
}
36579+
3655536580
function checkImportsForTypeOnlyConversion(sourceFile: SourceFile) {
3655636581
for (const statement of sourceFile.statements) {
36557-
if (
36558-
isImportDeclaration(statement) &&
36559-
statement.importClause &&
36560-
!statement.importClause.isTypeOnly &&
36561-
importClauseContainsReferencedImport(statement.importClause) &&
36562-
!isReferencedAliasDeclaration(statement.importClause, /*checkChildren*/ true) &&
36563-
!importClauseContainsConstEnumUsedAsValue(statement.importClause)
36564-
) {
36582+
if (canConvertImportDeclarationToTypeOnly(statement) || canConvertImportEqualsDeclarationToTypeOnly(statement)) {
3656536583
error(
3656636584
statement,
36567-
Diagnostics.This_import_is_never_used_as_a_value_and_must_use_import_type_because_the_importsNotUsedAsValues_is_set_to_error);
36585+
Diagnostics.This_import_is_never_used_as_a_value_and_must_use_import_type_because_importsNotUsedAsValues_is_set_to_error);
3656836586
}
3656936587
}
3657036588
}

src/compiler/diagnosticMessages.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,11 +1112,7 @@
11121112
"category": "Message",
11131113
"code": 1369
11141114
},
1115-
"Only ECMAScript imports may use 'import type'.": {
1116-
"category": "Error",
1117-
"code": 1370
1118-
},
1119-
"This import is never used as a value and must use 'import type' because the 'importsNotUsedAsValues' is set to 'error'.": {
1115+
"This import is never used as a value and must use 'import type' because 'importsNotUsedAsValues' is set to 'error'.": {
11201116
"category": "Error",
11211117
"code": 1371
11221118
},
@@ -1196,6 +1192,10 @@
11961192
"category": "Error",
11971193
"code": 1391
11981194
},
1195+
"An import alias cannot use 'import type'": {
1196+
"category": "Error",
1197+
"code": 1392
1198+
},
11991199
"The types of '{0}' are incompatible between these types.": {
12001200
"category": "Error",
12011201
"code": 2200

src/compiler/emitter.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3155,6 +3155,10 @@ namespace ts {
31553155
emitModifiers(node, node.modifiers);
31563156
emitTokenWithComment(SyntaxKind.ImportKeyword, node.modifiers ? node.modifiers.end : node.pos, writeKeyword, node);
31573157
writeSpace();
3158+
if (node.isTypeOnly) {
3159+
emitTokenWithComment(SyntaxKind.TypeKeyword, node.pos, writeKeyword, node);
3160+
writeSpace();
3161+
}
31583162
emit(node.name);
31593163
writeSpace();
31603164
emitTokenWithComment(SyntaxKind.EqualsToken, node.name.end, writePunctuation, node);

src/compiler/factory/nodeFactory.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3776,6 +3776,7 @@ namespace ts {
37763776
function createImportEqualsDeclaration(
37773777
decorators: readonly Decorator[] | undefined,
37783778
modifiers: readonly Modifier[] | undefined,
3779+
isTypeOnly: boolean,
37793780
name: string | Identifier,
37803781
moduleReference: ModuleReference
37813782
) {
@@ -3785,6 +3786,7 @@ namespace ts {
37853786
modifiers,
37863787
name
37873788
);
3789+
node.isTypeOnly = isTypeOnly;
37883790
node.moduleReference = moduleReference;
37893791
node.transformFlags |= propagateChildFlags(node.moduleReference);
37903792
if (!isExternalModuleReference(node.moduleReference)) node.transformFlags |= TransformFlags.ContainsTypeScript;
@@ -3797,14 +3799,16 @@ namespace ts {
37973799
node: ImportEqualsDeclaration,
37983800
decorators: readonly Decorator[] | undefined,
37993801
modifiers: readonly Modifier[] | undefined,
3802+
isTypeOnly: boolean,
38003803
name: Identifier,
38013804
moduleReference: ModuleReference
38023805
) {
38033806
return node.decorators !== decorators
38043807
|| node.modifiers !== modifiers
3808+
|| node.isTypeOnly !== isTypeOnly
38053809
|| node.name !== name
38063810
|| node.moduleReference !== moduleReference
3807-
? update(createImportEqualsDeclaration(decorators, modifiers, name, moduleReference), node)
3811+
? update(createImportEqualsDeclaration(decorators, modifiers, isTypeOnly, name, moduleReference), node)
38083812
: node;
38093813
}
38103814

@@ -5798,7 +5802,7 @@ namespace ts {
57985802
isTypeAliasDeclaration(node) ? updateTypeAliasDeclaration(node, node.decorators, modifiers, node.name, node.typeParameters, node.type) :
57995803
isEnumDeclaration(node) ? updateEnumDeclaration(node, node.decorators, modifiers, node.name, node.members) :
58005804
isModuleDeclaration(node) ? updateModuleDeclaration(node, node.decorators, modifiers, node.name, node.body) :
5801-
isImportEqualsDeclaration(node) ? updateImportEqualsDeclaration(node, node.decorators, modifiers, node.name, node.moduleReference) :
5805+
isImportEqualsDeclaration(node) ? updateImportEqualsDeclaration(node, node.decorators, modifiers, node.isTypeOnly, node.name, node.moduleReference) :
58025806
isImportDeclaration(node) ? updateImportDeclaration(node, node.decorators, modifiers, node.importClause, node.moduleSpecifier) :
58035807
isExportAssignment(node) ? updateExportAssignment(node, node.decorators, modifiers, node.expression) :
58045808
isExportDeclaration(node) ? updateExportDeclaration(node, node.decorators, modifiers, node.isTypeOnly, node.exportClause, node.moduleSpecifier) :

src/compiler/parser.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6929,11 +6929,8 @@ namespace ts {
69296929
parseExpected(SyntaxKind.EqualsToken);
69306930
const moduleReference = parseModuleReference();
69316931
parseSemicolon();
6932-
const node = factory.createImportEqualsDeclaration(decorators, modifiers, identifier, moduleReference);
6932+
const node = factory.createImportEqualsDeclaration(decorators, modifiers, isTypeOnly, identifier, moduleReference);
69336933
const finished = withJSDoc(finishNode(node, pos), hasJSDoc);
6934-
if (isTypeOnly) {
6935-
parseErrorAtRange(finished, Diagnostics.Only_ECMAScript_imports_may_use_import_type);
6936-
}
69376934
return finished;
69386935
}
69396936

src/compiler/transformers/declarations.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,7 @@ namespace ts {
681681
decl,
682682
/*decorators*/ undefined,
683683
decl.modifiers,
684+
decl.isTypeOnly,
684685
decl.name,
685686
factory.updateExternalModuleReference(decl.moduleReference, rewriteModuleSpecifier(decl, specifier))
686687
);

src/compiler/transformers/ts.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2973,6 +2973,11 @@ namespace ts {
29732973
* @param node The import equals declaration node.
29742974
*/
29752975
function visitImportEqualsDeclaration(node: ImportEqualsDeclaration): VisitResult<Statement> {
2976+
// Always elide type-only imports
2977+
if (node.isTypeOnly) {
2978+
return undefined;
2979+
}
2980+
29762981
if (isExternalModuleImportEqualsDeclaration(node)) {
29772982
const isReferenced = resolver.isReferencedAliasDeclaration(node);
29782983
// If the alias is unreferenced but we want to keep the import, replace with 'import "mod"'.

src/compiler/types.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2925,6 +2925,7 @@ namespace ts {
29252925
readonly kind: SyntaxKind.ImportEqualsDeclaration;
29262926
readonly parent: SourceFile | ModuleBlock;
29272927
readonly name: Identifier;
2928+
readonly isTypeOnly: boolean;
29282929

29292930
// 'EntityName' for an internal module reference, 'ExternalModuleReference' for an external
29302931
// module reference.
@@ -3037,6 +3038,7 @@ namespace ts {
30373038

30383039
export type TypeOnlyCompatibleAliasDeclaration =
30393040
| ImportClause
3041+
| ImportEqualsDeclaration
30403042
| NamespaceImport
30413043
| ImportOrExportSpecifier
30423044
;
@@ -7001,8 +7003,8 @@ namespace ts {
70017003
updateCaseBlock(node: CaseBlock, clauses: readonly CaseOrDefaultClause[]): CaseBlock;
70027004
createNamespaceExportDeclaration(name: string | Identifier): NamespaceExportDeclaration;
70037005
updateNamespaceExportDeclaration(node: NamespaceExportDeclaration, name: Identifier): NamespaceExportDeclaration;
7004-
createImportEqualsDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: string | Identifier, moduleReference: ModuleReference): ImportEqualsDeclaration;
7005-
updateImportEqualsDeclaration(node: ImportEqualsDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: Identifier, moduleReference: ModuleReference): ImportEqualsDeclaration;
7006+
createImportEqualsDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isTypeOnly: boolean, name: string | Identifier, moduleReference: ModuleReference): ImportEqualsDeclaration;
7007+
updateImportEqualsDeclaration(node: ImportEqualsDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isTypeOnly: boolean, name: Identifier, moduleReference: ModuleReference): ImportEqualsDeclaration;
70067008
createImportDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, importClause: ImportClause | undefined, moduleSpecifier: Expression): ImportDeclaration;
70077009
updateImportDeclaration(node: ImportDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, importClause: ImportClause | undefined, moduleSpecifier: Expression): ImportDeclaration;
70087010
createImportClause(isTypeOnly: boolean, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause;

src/compiler/utilitiesPublic.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1107,7 +1107,8 @@ namespace ts {
11071107
case SyntaxKind.NamespaceImport:
11081108
return (node as NamespaceImport).parent.isTypeOnly;
11091109
case SyntaxKind.ImportClause:
1110-
return (node as ImportClause).isTypeOnly;
1110+
case SyntaxKind.ImportEqualsDeclaration:
1111+
return (node as ImportClause | ImportEqualsDeclaration).isTypeOnly;
11111112
default:
11121113
return false;
11131114
}

src/compiler/visitorPublic.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,7 @@ namespace ts {
937937
return factory.updateImportEqualsDeclaration(<ImportEqualsDeclaration>node,
938938
nodesVisitor((<ImportEqualsDeclaration>node).decorators, visitor, isDecorator),
939939
nodesVisitor((<ImportEqualsDeclaration>node).modifiers, visitor, isModifier),
940+
(<ImportEqualsDeclaration>node).isTypeOnly,
940941
nodeVisitor((<ImportEqualsDeclaration>node).name, visitor, isIdentifier),
941942
nodeVisitor((<ImportEqualsDeclaration>node).moduleReference, visitor, isModuleReference));
942943

@@ -949,7 +950,7 @@ namespace ts {
949950

950951
case SyntaxKind.ImportClause:
951952
return factory.updateImportClause(<ImportClause>node,
952-
(node as ImportClause).isTypeOnly,
953+
(<ImportClause>node).isTypeOnly,
953954
nodeVisitor((<ImportClause>node).name, visitor, isIdentifier),
954955
nodeVisitor((<ImportClause>node).namedBindings, visitor, isNamedImportBindings));
955956

@@ -980,7 +981,7 @@ namespace ts {
980981
return factory.updateExportDeclaration(<ExportDeclaration>node,
981982
nodesVisitor((<ExportDeclaration>node).decorators, visitor, isDecorator),
982983
nodesVisitor((<ExportDeclaration>node).modifiers, visitor, isModifier),
983-
(node as ExportDeclaration).isTypeOnly,
984+
(<ExportDeclaration>node).isTypeOnly,
984985
nodeVisitor((<ExportDeclaration>node).exportClause, visitor, isNamedExportBindings),
985986
nodeVisitor((<ExportDeclaration>node).moduleSpecifier, visitor, isExpression));
986987

src/services/codefixes/convertToTypeOnlyImport.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* @internal */
22
namespace ts.codefix {
3-
const errorCodes = [Diagnostics.This_import_is_never_used_as_a_value_and_must_use_import_type_because_the_importsNotUsedAsValues_is_set_to_error.code];
3+
const errorCodes = [Diagnostics.This_import_is_never_used_as_a_value_and_must_use_import_type_because_importsNotUsedAsValues_is_set_to_error.code];
44
const fixId = "convertToTypeOnlyImport";
55
registerCodeFix({
66
errorCodes,

src/services/codefixes/fixInvalidImportSyntax.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ namespace ts.codefix {
1616
variations.push(createAction(context, sourceFile, node, factory.createImportEqualsDeclaration(
1717
/*decorators*/ undefined,
1818
/*modifiers*/ undefined,
19+
/*isTypeOnly*/ false,
1920
namespace.name,
2021
factory.createExternalModuleReference(node.moduleSpecifier)
2122
)));

src/services/codefixes/importFixes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,7 @@ namespace ts.codefix {
795795
? factory.createImportEqualsDeclaration(
796796
/*decorators*/ undefined,
797797
/*modifiers*/ undefined,
798+
typeOnly,
798799
factory.createIdentifier(namespaceLikeImport.name),
799800
factory.createExternalModuleReference(quotedModuleSpecifier))
800801
: factory.createImportDeclaration(

src/services/codefixes/requireInTs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ namespace ts.codefix {
2424
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, info: Info) {
2525
const { allowSyntheticDefaults, defaultImportName, namedImports, statement, required } = info;
2626
changes.replaceNode(sourceFile, statement, defaultImportName && !allowSyntheticDefaults
27-
? factory.createImportEqualsDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, defaultImportName, factory.createExternalModuleReference(required))
27+
? factory.createImportEqualsDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, defaultImportName, factory.createExternalModuleReference(required))
2828
: factory.createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, factory.createImportClause(/*isTypeOnly*/ false, defaultImportName, namedImports), required));
2929
}
3030

0 commit comments

Comments
 (0)