Skip to content

Preserve const enums should keep import refs #28498

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 30 additions & 16 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2380,7 +2380,7 @@ namespace ts {
return links.target;
}

function markExportAsReferenced(node: ImportEqualsDeclaration | ExportAssignment | ExportSpecifier) {
function markExportAsReferenced(node: ImportEqualsDeclaration | ExportSpecifier) {
const symbol = getSymbolOfNode(node);
const target = resolveAlias(symbol);
if (target) {
Expand All @@ -2402,15 +2402,10 @@ namespace ts {
links.referenced = true;
const node = getDeclarationOfAliasSymbol(symbol);
if (!node) return Debug.fail();
if (node.kind === SyntaxKind.ExportAssignment) {
// export default <symbol>
checkExpressionCached((<ExportAssignment>node).expression);
}
else if (node.kind === SyntaxKind.ExportSpecifier) {
// export { <symbol> } or export { <symbol> as foo }
checkExpressionCached((<ExportSpecifier>node).propertyName || (<ExportSpecifier>node).name);
}
else if (isInternalModuleImportEqualsDeclaration(node)) {
// We defer checking of the reference of an `import =` until the import itself is referenced,
// This way a chain of imports can be elided if ultimately the final input is only used in a type
// position.
if (isInternalModuleImportEqualsDeclaration(node)) {
// import foo = <symbol>
checkExpressionCached(<Expression>node.moduleReference);
}
Expand Down Expand Up @@ -17833,8 +17828,12 @@ namespace ts {
return type;
}

function isExportOrExportExpression(location: Node) {
return !!findAncestor(location, e => e.parent && isExportAssignment(e.parent) && e.parent.expression === e && isEntityNameExpression(e));
}

function markAliasReferenced(symbol: Symbol, location: Node) {
if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location) && !isConstEnumOrConstEnumOnlyModule(resolveAlias(symbol))) {
if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location) && ((compilerOptions.preserveConstEnums && isExportOrExportExpression(location)) || !isConstEnumOrConstEnumOnlyModule(resolveAlias(symbol)))) {
markAliasSymbolAsReferenced(symbol);
}
}
Expand Down Expand Up @@ -20345,8 +20344,8 @@ namespace ts {
// if jsx emit was not react as there wont be error being emitted
reactSym.isReferenced = SymbolFlags.All;

// If react symbol is alias, mark it as referenced
if (reactSym.flags & SymbolFlags.Alias && !isConstEnumOrConstEnumOnlyModule(resolveAlias(reactSym))) {
// If react symbol is alias, mark it as refereced
if (reactSym.flags & SymbolFlags.Alias) {
markAliasSymbolAsReferenced(reactSym);
}
}
Expand Down Expand Up @@ -24897,7 +24896,7 @@ namespace ts {
return result;
}

function checkExpressionCached(node: Expression, checkMode?: CheckMode): Type {
function checkExpressionCached(node: Expression | QualifiedName, checkMode?: CheckMode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
if (checkMode && checkMode !== CheckMode.Normal) {
Expand Down Expand Up @@ -25225,7 +25224,8 @@ namespace ts {
(node.parent.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>node.parent).expression === node) ||
(node.parent.kind === SyntaxKind.ElementAccessExpression && (<ElementAccessExpression>node.parent).expression === node) ||
((node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName) && isInRightSideOfImportOrExportAssignment(<Identifier>node) ||
(node.parent.kind === SyntaxKind.TypeQuery && (<TypeQueryNode>node.parent).exprName === node));
(node.parent.kind === SyntaxKind.TypeQuery && (<TypeQueryNode>node.parent).exprName === node)) ||
(node.parent.kind === SyntaxKind.ExportSpecifier && (compilerOptions.preserveConstEnums || node.flags & NodeFlags.Ambient)); // We allow reexporting const enums

if (!ok) {
error(node, Diagnostics.const_enums_can_only_be_used_in_property_or_index_access_expressions_or_the_right_hand_side_of_an_import_declaration_or_export_assignment_or_type_query);
Expand Down Expand Up @@ -30153,6 +30153,10 @@ namespace ts {
}
else {
markExportAsReferenced(node);
const target = symbol && (symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol);
if (!target || target === unknownSymbol || target.flags & SymbolFlags.Value) {
checkExpressionCached(node.propertyName || node.name);
}
}
}
}
Expand All @@ -30179,7 +30183,17 @@ namespace ts {
grammarErrorOnFirstToken(node, Diagnostics.An_export_assignment_cannot_have_modifiers);
}
if (node.expression.kind === SyntaxKind.Identifier) {
markExportAsReferenced(node);
const id = node.expression as Identifier;
const sym = resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, node);
if (sym) {
markAliasReferenced(sym, id);
// If not a value, we're interpreting the identifier as a type export, along the lines of (`export { Id as default }`)
const target = sym.flags & SymbolFlags.Alias ? resolveAlias(sym) : sym;
if (target === unknownSymbol || target.flags & SymbolFlags.Value) {
// However if it is a value, we need to check it's being used correctly
checkExpressionCached(node.expression);
}
}

if (getEmitDeclarations(compilerOptions)) {
collectLinkedAliases(node.expression as Identifier, /*setVisibility*/ true);
Expand Down
40 changes: 40 additions & 0 deletions tests/baselines/reference/amdModuleConstEnumUsage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//// [tests/cases/compiler/amdModuleConstEnumUsage.ts] ////

//// [cc.ts]
export const enum CharCode {
A,
B
}
//// [file.ts]
import { CharCode } from 'defs/cc';
export class User {
method(input: number) {
if (CharCode.A === input) {}
}
}


//// [cc.js]
define(["require", "exports"], function (require, exports) {
"use strict";
exports.__esModule = true;
var CharCode;
(function (CharCode) {
CharCode[CharCode["A"] = 0] = "A";
CharCode[CharCode["B"] = 1] = "B";
})(CharCode = exports.CharCode || (exports.CharCode = {}));
});
//// [file.js]
define(["require", "exports"], function (require, exports) {
"use strict";
exports.__esModule = true;
var User = /** @class */ (function () {
function User() {
}
User.prototype.method = function (input) {
if (0 /* A */ === input) { }
};
return User;
}());
exports.User = User;
});
29 changes: 29 additions & 0 deletions tests/baselines/reference/amdModuleConstEnumUsage.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
=== /proj/defs/cc.ts ===
export const enum CharCode {
>CharCode : Symbol(CharCode, Decl(cc.ts, 0, 0))

A,
>A : Symbol(CharCode.A, Decl(cc.ts, 0, 28))

B
>B : Symbol(CharCode.B, Decl(cc.ts, 1, 6))
}
=== /proj/component/file.ts ===
import { CharCode } from 'defs/cc';
>CharCode : Symbol(CharCode, Decl(file.ts, 0, 8))

export class User {
>User : Symbol(User, Decl(file.ts, 0, 35))

method(input: number) {
>method : Symbol(User.method, Decl(file.ts, 1, 19))
>input : Symbol(input, Decl(file.ts, 2, 11))

if (CharCode.A === input) {}
>CharCode.A : Symbol(CharCode.A, Decl(cc.ts, 0, 28))
>CharCode : Symbol(CharCode, Decl(file.ts, 0, 8))
>A : Symbol(CharCode.A, Decl(cc.ts, 0, 28))
>input : Symbol(input, Decl(file.ts, 2, 11))
}
}

30 changes: 30 additions & 0 deletions tests/baselines/reference/amdModuleConstEnumUsage.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
=== /proj/defs/cc.ts ===
export const enum CharCode {
>CharCode : CharCode

A,
>A : CharCode.A

B
>B : CharCode.B
}
=== /proj/component/file.ts ===
import { CharCode } from 'defs/cc';
>CharCode : typeof CharCode

export class User {
>User : User

method(input: number) {
>method : (input: number) => void
>input : number

if (CharCode.A === input) {}
>CharCode.A === input : boolean
>CharCode.A : CharCode.A
>CharCode : typeof CharCode
>A : CharCode.A
>input : number
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//// [tests/cases/compiler/constEnumNoPreserveDeclarationReexport.ts] ////

//// [ConstEnum.d.ts]
export const enum MyConstEnum {
Foo,
Bar
}
//// [ImportExport.d.ts]
import { MyConstEnum } from './ConstEnum';
export default MyConstEnum;
//// [ReExport.d.ts]
export { MyConstEnum as default } from './ConstEnum';
//// [usages.ts]
import {MyConstEnum} from "./ConstEnum";
import AlsoEnum from "./ImportExport";
import StillEnum from "./ReExport";

MyConstEnum.Foo;
AlsoEnum.Foo;
StillEnum.Foo;


//// [usages.js]
"use strict";
exports.__esModule = true;
0 /* Foo */;
0 /* Foo */;
0 /* Foo */;
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
=== tests/cases/compiler/ConstEnum.d.ts ===
export const enum MyConstEnum {
>MyConstEnum : Symbol(MyConstEnum, Decl(ConstEnum.d.ts, 0, 0))

Foo,
>Foo : Symbol(MyConstEnum.Foo, Decl(ConstEnum.d.ts, 0, 31))

Bar
>Bar : Symbol(MyConstEnum.Bar, Decl(ConstEnum.d.ts, 1, 8))
}
=== tests/cases/compiler/ImportExport.d.ts ===
import { MyConstEnum } from './ConstEnum';
>MyConstEnum : Symbol(MyConstEnum, Decl(ImportExport.d.ts, 0, 8))

export default MyConstEnum;
>MyConstEnum : Symbol(MyConstEnum, Decl(ImportExport.d.ts, 0, 8))

=== tests/cases/compiler/ReExport.d.ts ===
export { MyConstEnum as default } from './ConstEnum';
>MyConstEnum : Symbol(MyConstEnum, Decl(ConstEnum.d.ts, 0, 0))
>default : Symbol(default, Decl(ReExport.d.ts, 0, 8))

=== tests/cases/compiler/usages.ts ===
import {MyConstEnum} from "./ConstEnum";
>MyConstEnum : Symbol(MyConstEnum, Decl(usages.ts, 0, 8))

import AlsoEnum from "./ImportExport";
>AlsoEnum : Symbol(AlsoEnum, Decl(usages.ts, 1, 6))

import StillEnum from "./ReExport";
>StillEnum : Symbol(StillEnum, Decl(usages.ts, 2, 6))

MyConstEnum.Foo;
>MyConstEnum.Foo : Symbol(MyConstEnum.Foo, Decl(ConstEnum.d.ts, 0, 31))
>MyConstEnum : Symbol(MyConstEnum, Decl(usages.ts, 0, 8))
>Foo : Symbol(MyConstEnum.Foo, Decl(ConstEnum.d.ts, 0, 31))

AlsoEnum.Foo;
>AlsoEnum.Foo : Symbol(MyConstEnum.Foo, Decl(ConstEnum.d.ts, 0, 31))
>AlsoEnum : Symbol(AlsoEnum, Decl(usages.ts, 1, 6))
>Foo : Symbol(MyConstEnum.Foo, Decl(ConstEnum.d.ts, 0, 31))

StillEnum.Foo;
>StillEnum.Foo : Symbol(MyConstEnum.Foo, Decl(ConstEnum.d.ts, 0, 31))
>StillEnum : Symbol(StillEnum, Decl(usages.ts, 2, 6))
>Foo : Symbol(MyConstEnum.Foo, Decl(ConstEnum.d.ts, 0, 31))

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
=== tests/cases/compiler/ConstEnum.d.ts ===
export const enum MyConstEnum {
>MyConstEnum : MyConstEnum

Foo,
>Foo : MyConstEnum

Bar
>Bar : MyConstEnum
}
=== tests/cases/compiler/ImportExport.d.ts ===
import { MyConstEnum } from './ConstEnum';
>MyConstEnum : typeof MyConstEnum

export default MyConstEnum;
>MyConstEnum : MyConstEnum

=== tests/cases/compiler/ReExport.d.ts ===
export { MyConstEnum as default } from './ConstEnum';
>MyConstEnum : typeof import("tests/cases/compiler/ConstEnum").MyConstEnum
>default : typeof import("tests/cases/compiler/ConstEnum").MyConstEnum

=== tests/cases/compiler/usages.ts ===
import {MyConstEnum} from "./ConstEnum";
>MyConstEnum : typeof MyConstEnum

import AlsoEnum from "./ImportExport";
>AlsoEnum : typeof MyConstEnum

import StillEnum from "./ReExport";
>StillEnum : typeof MyConstEnum

MyConstEnum.Foo;
>MyConstEnum.Foo : MyConstEnum
>MyConstEnum : typeof MyConstEnum
>Foo : MyConstEnum

AlsoEnum.Foo;
>AlsoEnum.Foo : MyConstEnum
>AlsoEnum : typeof MyConstEnum
>Foo : MyConstEnum

StillEnum.Foo;
>StillEnum.Foo : MyConstEnum
>StillEnum : typeof MyConstEnum
>Foo : MyConstEnum

32 changes: 32 additions & 0 deletions tests/baselines/reference/constEnumPreserveEmitReexport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//// [tests/cases/compiler/constEnumPreserveEmitReexport.ts] ////

//// [ConstEnum.ts]
export const enum MyConstEnum {
Foo,
Bar
};
//// [ImportExport.ts]
import { MyConstEnum } from './ConstEnum';
export default MyConstEnum;
//// [ReExport.ts]
export { MyConstEnum as default } from './ConstEnum';

//// [ConstEnum.js]
"use strict";
exports.__esModule = true;
var MyConstEnum;
(function (MyConstEnum) {
MyConstEnum[MyConstEnum["Foo"] = 0] = "Foo";
MyConstEnum[MyConstEnum["Bar"] = 1] = "Bar";
})(MyConstEnum = exports.MyConstEnum || (exports.MyConstEnum = {}));
;
//// [ImportExport.js]
"use strict";
exports.__esModule = true;
var ConstEnum_1 = require("./ConstEnum");
exports["default"] = ConstEnum_1.MyConstEnum;
//// [ReExport.js]
"use strict";
exports.__esModule = true;
var ConstEnum_1 = require("./ConstEnum");
exports["default"] = ConstEnum_1.MyConstEnum;
23 changes: 23 additions & 0 deletions tests/baselines/reference/constEnumPreserveEmitReexport.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
=== tests/cases/compiler/ConstEnum.ts ===
export const enum MyConstEnum {
>MyConstEnum : Symbol(MyConstEnum, Decl(ConstEnum.ts, 0, 0))

Foo,
>Foo : Symbol(MyConstEnum.Foo, Decl(ConstEnum.ts, 0, 31))

Bar
>Bar : Symbol(MyConstEnum.Bar, Decl(ConstEnum.ts, 1, 8))

};
=== tests/cases/compiler/ImportExport.ts ===
import { MyConstEnum } from './ConstEnum';
>MyConstEnum : Symbol(MyConstEnum, Decl(ImportExport.ts, 0, 8))

export default MyConstEnum;
>MyConstEnum : Symbol(MyConstEnum, Decl(ImportExport.ts, 0, 8))

=== tests/cases/compiler/ReExport.ts ===
export { MyConstEnum as default } from './ConstEnum';
>MyConstEnum : Symbol(MyConstEnum, Decl(ConstEnum.ts, 0, 0))
>default : Symbol(default, Decl(ReExport.ts, 0, 8))

Loading