Skip to content

Commit f64dace

Browse files
authored
fix(jsii): fail compilation when two or more enum members have same val (#3412)
If two enum members have the same value, only the first one will be retained. This is a bit of an issue as we are renaming enum members: the named version will never appear in the assembly, and so not work over jsii. What's worse, if we deprecate-and-strip the original one, neither of the enum members will appear. Addressing the issue by failing the compilation by adding validation for enum values Related change in CDK aws/aws-cdk#19320 Fixes #2782. --- By submitting this pull request, I confirm that my contribution is made under the terms of the [Apache 2.0 license]. [Apache 2.0 license]: https://www.apache.org/licenses/LICENSE-2.0
1 parent 6d9dda5 commit f64dace

File tree

5 files changed

+105
-1
lines changed

5 files changed

+105
-1
lines changed

packages/jsii/lib/assembler.ts

+54
Original file line numberDiff line numberDiff line change
@@ -1738,6 +1738,9 @@ export class Assembler implements Emitter {
17381738
return undefined;
17391739
}
17401740

1741+
// check the enum to see if there are duplicate enum values
1742+
this.assertNoDuplicateEnumValues(decl);
1743+
17411744
this._warnAboutReservedWords(symbol);
17421745

17431746
const flags = ts.getCombinedModifierFlags(decl);
@@ -1791,6 +1794,57 @@ export class Assembler implements Emitter {
17911794
return jsiiType;
17921795
}
17931796

1797+
private assertNoDuplicateEnumValues(decl: ts.EnumDeclaration): void {
1798+
type EnumValue = {
1799+
name: string;
1800+
value: string;
1801+
decl: ts.DeclarationName | undefined;
1802+
};
1803+
1804+
const enumValues = decl.members
1805+
.filter((m) => m.initializer)
1806+
.map((member): EnumValue => {
1807+
return {
1808+
value: member.initializer!.getText(),
1809+
name: member.name.getText(),
1810+
decl: ts.getNameOfDeclaration(member),
1811+
};
1812+
});
1813+
1814+
const hasDuplicateEnumValues = enumValues.some(
1815+
(val, _, arr) => arr.filter((e) => val.value === e.value).length > 1,
1816+
);
1817+
1818+
if (hasDuplicateEnumValues) {
1819+
const enumValueMap = enumValues.reduce<Record<string, EnumValue[]>>(
1820+
(acc, val) => {
1821+
if (!acc[val.value]) {
1822+
acc[val.value] = [];
1823+
}
1824+
acc[val.value].push(val);
1825+
return acc;
1826+
},
1827+
{},
1828+
);
1829+
for (const duplicateValue of Object.keys(enumValueMap)) {
1830+
if (enumValueMap[duplicateValue].length > 1) {
1831+
const err = JsiiDiagnostic.JSII_1004_DUPLICATE_ENUM_VALUE.create(
1832+
enumValueMap[duplicateValue][0].decl!,
1833+
duplicateValue,
1834+
enumValueMap[duplicateValue].map((v) => v.name),
1835+
);
1836+
for (let i = 1; i < enumValueMap[duplicateValue].length; i++) {
1837+
err.addRelatedInformation(
1838+
enumValueMap[duplicateValue][i].decl!,
1839+
'The conflicting declaration is here',
1840+
);
1841+
}
1842+
this._diagnostics.push(err);
1843+
}
1844+
}
1845+
}
1846+
}
1847+
17941848
/**
17951849
* Return docs for a symbol
17961850
*/

packages/jsii/lib/jsii-diagnostic.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export class Code<
109109
*
110110
* @param code the numeric code for the diagnostic
111111
* @param name the symbolic name for the diagnostic
112-
* @param defaultCategory the default category this diagnostic ransk in
112+
* @param defaultCategory the default category this diagnostic ranks in
113113
* @param formatter a message formatter for easy creation of diagnostics
114114
*/
115115
private constructor(
@@ -292,6 +292,15 @@ export class JsiiDiagnostic implements ts.Diagnostic {
292292
name: 'typescript-restrictions/unsupported-type',
293293
});
294294

295+
public static readonly JSII_1004_DUPLICATE_ENUM_VALUE = Code.error({
296+
code: 1004,
297+
formatter: (enumValue: string, enumMemberNames: string[]) =>
298+
`Value ${enumValue} is used for multiple enum values: ${enumMemberNames.join(
299+
', ',
300+
)}`,
301+
name: 'typescript-restrictions/duplicate-enum-value',
302+
});
303+
295304
//////////////////////////////////////////////////////////////////////////////
296305
// 2000 => 2999 -- RESERVED
297306

packages/jsii/test/__snapshots__/negatives.test.js.snap

+22
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/jsii/test/enums.test.ts

+13
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,16 @@ test('enums can have a mix of letters and number', () => {
5757
{ name: 'IB3M' },
5858
]);
5959
});
60+
61+
test('enums with the same assigned value should fail', () => {
62+
expect(() =>
63+
sourceToAssemblyHelper(`
64+
export enum Foo {
65+
BAR = 'Bar',
66+
BAR_DUPE = 'Bar',
67+
BAZ = 'Baz',
68+
BAZ_DUPE = 'Baz',
69+
}
70+
`),
71+
).toThrow(/There were compiler errors/);
72+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export enum Foo {
2+
FOO = 'foo',
3+
FOO_DUPLICATE = 'foo',
4+
BAR = 'bar',
5+
BAR_COPY = 'bar',
6+
}

0 commit comments

Comments
 (0)