Skip to content

Commit f70b068

Browse files
authored
isolatedModules errors for non-literal enum initializers (#56736)
1 parent af81456 commit f70b068

9 files changed

+274
-7
lines changed

src/compiler/checker.ts

+35-6
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,7 @@ import {
719719
isStringOrNumericLiteralLike,
720720
isSuperCall,
721721
isSuperProperty,
722+
isSyntacticallyString,
722723
isTaggedTemplateExpression,
723724
isTemplateSpan,
724725
isThisContainerOrFunctionBlock,
@@ -45675,15 +45676,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4567545676
if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputed)) {
4567645677
nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed;
4567745678
let autoValue: number | undefined = 0;
45679+
let previous: EnumMember | undefined;
4567845680
for (const member of node.members) {
45679-
const value = computeMemberValue(member, autoValue);
45681+
const value = computeMemberValue(member, autoValue, previous);
4568045682
getNodeLinks(member).enumMemberValue = value;
4568145683
autoValue = typeof value === "number" ? value + 1 : undefined;
45684+
previous = member;
4568245685
}
4568345686
}
4568445687
}
4568545688

45686-
function computeMemberValue(member: EnumMember, autoValue: number | undefined) {
45689+
function computeMemberValue(member: EnumMember, autoValue: number | undefined, previous: EnumMember | undefined) {
4568745690
if (isComputedNonLiteralName(member.name)) {
4568845691
error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums);
4568945692
}
@@ -45705,11 +45708,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4570545708
// If the member is the first member in the enum declaration, it is assigned the value zero.
4570645709
// Otherwise, it is assigned the value of the immediately preceding member plus one, and an error
4570745710
// occurs if the immediately preceding member is not a constant enum member.
45708-
if (autoValue !== undefined) {
45709-
return autoValue;
45711+
if (autoValue === undefined) {
45712+
error(member.name, Diagnostics.Enum_member_must_have_initializer);
45713+
return undefined;
4571045714
}
45711-
error(member.name, Diagnostics.Enum_member_must_have_initializer);
45712-
return undefined;
45715+
if (getIsolatedModules(compilerOptions) && previous?.initializer && !isSyntacticallyNumericConstant(previous.initializer)) {
45716+
error(
45717+
member.name,
45718+
Diagnostics.Enum_member_following_a_non_literal_numeric_member_must_have_an_initializer_when_isolatedModules_is_enabled,
45719+
);
45720+
}
45721+
return autoValue;
4571345722
}
4571445723

4571545724
function computeConstantValue(member: EnumMember): string | number | undefined {
@@ -45725,6 +45734,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4572545734
Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value,
4572645735
);
4572745736
}
45737+
else if (getIsolatedModules(compilerOptions) && typeof value === "string" && !isSyntacticallyString(initializer)) {
45738+
error(
45739+
initializer,
45740+
Diagnostics._0_has_a_string_type_but_must_have_syntactically_recognizable_string_syntax_when_isolatedModules_is_enabled,
45741+
`${idText(member.parent.name)}.${getTextOfPropertyName(member.name)}`,
45742+
);
45743+
}
4572845744
}
4572945745
else if (isConstEnum) {
4573045746
error(initializer, Diagnostics.const_enum_member_initializers_must_be_constant_expressions);
@@ -45738,6 +45754,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4573845754
return value;
4573945755
}
4574045756

45757+
function isSyntacticallyNumericConstant(expr: Expression): boolean {
45758+
expr = skipOuterExpressions(expr);
45759+
switch (expr.kind) {
45760+
case SyntaxKind.PrefixUnaryExpression:
45761+
return isSyntacticallyNumericConstant((expr as PrefixUnaryExpression).operand);
45762+
case SyntaxKind.BinaryExpression:
45763+
return isSyntacticallyNumericConstant((expr as BinaryExpression).left) && isSyntacticallyNumericConstant((expr as BinaryExpression).right);
45764+
case SyntaxKind.NumericLiteral:
45765+
return true;
45766+
}
45767+
return false;
45768+
}
45769+
4574145770
function evaluate(expr: Expression, location?: Declaration): string | number | undefined {
4574245771
switch (expr.kind) {
4574345772
case SyntaxKind.PrefixUnaryExpression:

src/compiler/diagnosticMessages.json

+8
Original file line numberDiff line numberDiff line change
@@ -7976,5 +7976,13 @@
79767976
"'await using' statements cannot be used inside a class static block.": {
79777977
"category": "Error",
79787978
"code": 18054
7979+
},
7980+
"'{0}' has a string type, but must have syntactically recognizable string syntax when 'isolatedModules' is enabled.": {
7981+
"category": "Error",
7982+
"code": 18055
7983+
},
7984+
"Enum member following a non-literal numeric member must have an initializer when 'isolatedModules' is enabled.": {
7985+
"category": "Error",
7986+
"code": 18056
79797987
}
79807988
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
bad.ts(4,5): error TS18056: Enum member following a non-literal numeric member must have an initializer when 'isolatedModules' is enabled.
2+
3+
4+
==== ./helpers.ts (0 errors) ====
5+
export const foo = 2;
6+
7+
==== ./bad.ts (1 errors) ====
8+
import { foo } from "./helpers";
9+
enum A {
10+
a = foo,
11+
b,
12+
~
13+
!!! error TS18056: Enum member following a non-literal numeric member must have an initializer when 'isolatedModules' is enabled.
14+
}
15+
16+
==== ./good.ts (0 errors) ====
17+
import { foo } from "./helpers";
18+
enum A {
19+
a = foo,
20+
b = 3,
21+
}
22+
enum B {
23+
a = 1 + 1,
24+
b,
25+
}
26+
enum C {
27+
a = +2,
28+
b,
29+
}
30+
enum D {
31+
a = (2),
32+
b,
33+
}
34+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//// [tests/cases/compiler/enumNoInitializerFollowsNonLiteralInitializer.ts] ////
2+
3+
//// [helpers.ts]
4+
export const foo = 2;
5+
6+
//// [bad.ts]
7+
import { foo } from "./helpers";
8+
enum A {
9+
a = foo,
10+
b,
11+
}
12+
13+
//// [good.ts]
14+
import { foo } from "./helpers";
15+
enum A {
16+
a = foo,
17+
b = 3,
18+
}
19+
enum B {
20+
a = 1 + 1,
21+
b,
22+
}
23+
enum C {
24+
a = +2,
25+
b,
26+
}
27+
enum D {
28+
a = (2),
29+
b,
30+
}
31+
32+
33+
//// [helpers.js]
34+
"use strict";
35+
Object.defineProperty(exports, "__esModule", { value: true });
36+
exports.foo = void 0;
37+
exports.foo = 2;
38+
//// [bad.js]
39+
"use strict";
40+
Object.defineProperty(exports, "__esModule", { value: true });
41+
var helpers_1 = require("./helpers");
42+
var A;
43+
(function (A) {
44+
A[A["a"] = 2] = "a";
45+
A[A["b"] = 3] = "b";
46+
})(A || (A = {}));
47+
//// [good.js]
48+
"use strict";
49+
Object.defineProperty(exports, "__esModule", { value: true });
50+
var helpers_1 = require("./helpers");
51+
var A;
52+
(function (A) {
53+
A[A["a"] = 2] = "a";
54+
A[A["b"] = 3] = "b";
55+
})(A || (A = {}));
56+
var B;
57+
(function (B) {
58+
B[B["a"] = 2] = "a";
59+
B[B["b"] = 3] = "b";
60+
})(B || (B = {}));
61+
var C;
62+
(function (C) {
63+
C[C["a"] = 2] = "a";
64+
C[C["b"] = 3] = "b";
65+
})(C || (C = {}));
66+
var D;
67+
(function (D) {
68+
D[D["a"] = 2] = "a";
69+
D[D["b"] = 3] = "b";
70+
})(D || (D = {}));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
bad.ts(3,8): error TS18055: 'A.a' has a string type, but must have syntactically recognizable string syntax when 'isolatedModules' is enabled.
2+
3+
4+
==== ./helpers.ts (0 errors) ====
5+
export const foo = 2;
6+
export const bar = "bar";
7+
8+
==== ./bad.ts (1 errors) ====
9+
import { bar } from "./helpers";
10+
enum A {
11+
a = bar,
12+
~~~
13+
!!! error TS18055: 'A.a' has a string type, but must have syntactically recognizable string syntax when 'isolatedModules' is enabled.
14+
}
15+
16+
==== ./good.ts (0 errors) ====
17+
import { foo } from "./helpers";
18+
enum A {
19+
a = `${foo}`,
20+
b = "" + 2,
21+
c = 2 + "",
22+
d = ("foo"),
23+
}
24+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//// [tests/cases/compiler/enumWithNonLiteralStringInitializer.ts] ////
2+
3+
//// [helpers.ts]
4+
export const foo = 2;
5+
export const bar = "bar";
6+
7+
//// [bad.ts]
8+
import { bar } from "./helpers";
9+
enum A {
10+
a = bar,
11+
}
12+
13+
//// [good.ts]
14+
import { foo } from "./helpers";
15+
enum A {
16+
a = `${foo}`,
17+
b = "" + 2,
18+
c = 2 + "",
19+
d = ("foo"),
20+
}
21+
22+
23+
//// [helpers.js]
24+
"use strict";
25+
Object.defineProperty(exports, "__esModule", { value: true });
26+
exports.bar = exports.foo = void 0;
27+
exports.foo = 2;
28+
exports.bar = "bar";
29+
//// [bad.js]
30+
"use strict";
31+
Object.defineProperty(exports, "__esModule", { value: true });
32+
var helpers_1 = require("./helpers");
33+
var A;
34+
(function (A) {
35+
A["a"] = "bar";
36+
})(A || (A = {}));
37+
//// [good.js]
38+
"use strict";
39+
Object.defineProperty(exports, "__esModule", { value: true });
40+
var helpers_1 = require("./helpers");
41+
var A;
42+
(function (A) {
43+
A["a"] = "2";
44+
A["b"] = "2";
45+
A["c"] = "2";
46+
A["d"] = "foo";
47+
})(A || (A = {}));

tests/baselines/reference/isolatedModulesGlobalNamespacesAndEnums.errors.txt

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
enum2.ts(2,9): error TS18055: 'Enum.D' has a string type, but must have syntactically recognizable string syntax when 'isolatedModules' is enabled.
12
enum2.ts(3,9): error TS1281: Cannot access 'A' from another file without qualification when 'isolatedModules' is enabled. Use 'Enum.A' instead.
23
enum2.ts(4,9): error TS1281: Cannot access 'X' from another file without qualification when 'isolatedModules' is enabled. Use 'Enum.X' instead.
34
script-namespaces.ts(1,11): error TS1280: Namespaces are not allowed in global script files when 'isolatedModules' is enabled. If this file is not intended to be a global script, set 'moduleDetection' to 'force' or add an empty 'export {}' statement.
@@ -26,9 +27,11 @@ script-namespaces.ts(1,11): error TS1280: Namespaces are not allowed in global s
2627
declare enum Enum { X = 1_000_000 }
2728
const d = 'd';
2829

29-
==== enum2.ts (2 errors) ====
30+
==== enum2.ts (3 errors) ====
3031
enum Enum {
3132
D = d,
33+
~
34+
!!! error TS18055: 'Enum.D' has a string type, but must have syntactically recognizable string syntax when 'isolatedModules' is enabled.
3235
E = A, // error
3336
~
3437
!!! error TS1281: Cannot access 'A' from another file without qualification when 'isolatedModules' is enabled. Use 'Enum.A' instead.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// @isolatedModules: true
2+
// @noTypesAndSymbols: true
3+
4+
// @filename: ./helpers.ts
5+
export const foo = 2;
6+
7+
// @filename: ./bad.ts
8+
import { foo } from "./helpers";
9+
enum A {
10+
a = foo,
11+
b,
12+
}
13+
14+
// @filename: ./good.ts
15+
import { foo } from "./helpers";
16+
enum A {
17+
a = foo,
18+
b = 3,
19+
}
20+
enum B {
21+
a = 1 + 1,
22+
b,
23+
}
24+
enum C {
25+
a = +2,
26+
b,
27+
}
28+
enum D {
29+
a = (2),
30+
b,
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// @isolatedModules: true
2+
// @noTypesAndSymbols: true
3+
4+
// @filename: ./helpers.ts
5+
export const foo = 2;
6+
export const bar = "bar";
7+
8+
// @filename: ./bad.ts
9+
import { bar } from "./helpers";
10+
enum A {
11+
a = bar,
12+
}
13+
14+
// @filename: ./good.ts
15+
import { foo } from "./helpers";
16+
enum A {
17+
a = `${foo}`,
18+
b = "" + 2,
19+
c = 2 + "",
20+
d = ("foo"),
21+
}

0 commit comments

Comments
 (0)