Skip to content

Commit

Permalink
fix(jsii): fail compilation when two or more enum members have same v…
Browse files Browse the repository at this point in the history
…alue

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

Fixes aws#2782.
  • Loading branch information
yuth committed Mar 7, 2022
1 parent c613fce commit c32d7f6
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 0 deletions.
57 changes: 57 additions & 0 deletions packages/jsii/lib/assembler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1744,6 +1744,9 @@ export class Assembler implements Emitter {
return Promise.resolve(undefined);
}

// check the enum to see if there are duplicate enum values
this.assertNoDuplicateEnumValues(decl);

this._warnAboutReservedWords(symbol);

const flags = ts.getCombinedModifierFlags(decl);
Expand Down Expand Up @@ -1788,6 +1791,60 @@ export class Assembler implements Emitter {
return Promise.resolve(jsiiType);
}

private assertNoDuplicateEnumValues(decl: ts.EnumDeclaration): void {
type EnumValue = {
name: string;
value: string;
decl: ts.DeclarationName | undefined;
};

const enumValues = decl.members
.filter((m) => m.initializer)
.map((member): EnumValue => {
return {
value: member.initializer!.getText(),
name: member.name.getText(),
decl: ts.getNameOfDeclaration(member),
};
});

const hasDuplicateEnumValues = enumValues.some(
(val, _, arr) => arr.filter((e) => val.value === e.value).length > 1,
);

if (hasDuplicateEnumValues) {
const enumValueMap = enumValues.reduce<Record<string, EnumValue[]>>(
(acc, val) => {
if (!acc[val.value]) {
acc[val.value] = [];
}
acc[val.value].push(val);
return acc;
},
{},
);
for (const duplicateValue of Object.keys(enumValueMap)) {
if (enumValueMap[duplicateValue].length > 1) {
const err = JsiiDiagnostic.JSII_1004_DUPLICATE_ENUM_VALUE.create(
enumValueMap[duplicateValue][0].decl!,
`Value ${duplicateValue} is used for multiple enum values: ${enumValueMap[
duplicateValue
]
.map((e: any) => e.name)
.join(', ')}`,
);
for (let i = 1; i < enumValueMap[duplicateValue].length; i++) {
err.addRelatedInformation(
enumValueMap[duplicateValue][i].decl!,
'Same value assigned here',
);
}
this._diagnostics.push(err);
}
}
}
}

/**
* Return docs for a symbol
*/
Expand Down
6 changes: 6 additions & 0 deletions packages/jsii/lib/jsii-diagnostic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,12 @@ export class JsiiDiagnostic implements ts.Diagnostic {
name: 'typescript-restrictions/unsupported-type',
});

public static readonly JSII_1004_DUPLICATE_ENUM_VALUE = Code.error({
code: 1004,
formatter: (messageText) => messageText,
name: 'typescript-restrictions/duplicate-enum-value',
});

//////////////////////////////////////////////////////////////////////////////
// 2000 => 2999 -- RESERVED

Expand Down
13 changes: 13 additions & 0 deletions packages/jsii/test/enums.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,16 @@ test('enums can have a mix of letters and number', async () => {
{ name: 'IB3M' },
]);
});

test('enums with the same assigned value should fail', async () => {
await expect(() =>
sourceToAssemblyHelper(`
export enum Foo {
BAR = 'Bar',
BAR_DUPE = 'Bar',
BAZ = 'Baz',
BAZ_DUPE = 'Baz',
}
`),
).rejects.toThrowError('There were compiler errors');
});

0 comments on commit c32d7f6

Please sign in to comment.