Skip to content

Commit 907df8f

Browse files
committed
fix(jsii): Correctly handle singleton enums
When an enum has only one option, TypeScript handles it in a special way and tries very hard to hide the enum declaration in favor of the sole member. This caused incorrect type names and kinds to be emitted in the JSII assembly, resulting in incorrect behavior. This uses a non-public part of the TSC API (possibly an omission from the hand-written type model), so it includes an additional guard check to fail explicitly in case the API's behavior happens to change in a breaking way. Fixes #231
1 parent e804cab commit 907df8f

File tree

3 files changed

+174
-7
lines changed

3 files changed

+174
-7
lines changed

packages/jsii-calc/lib/compliance.ts

+34
Original file line numberDiff line numberDiff line change
@@ -1692,3 +1692,37 @@ export class WithPrivatePropertyInConstructor {
16921692
return this.privateField === 'Success!';
16931693
}
16941694
}
1695+
1696+
/**
1697+
* Verifies that singleton enums are handled correctly
1698+
*
1699+
* https://github.com/awslabs/jsii/issues/231
1700+
*/
1701+
export class SingletonString {
1702+
private constructor() { }
1703+
1704+
public isSingletonString(value: string): boolean {
1705+
return value === SingletonStringEnum.SingletonString;
1706+
}
1707+
}
1708+
/** A singleton string */
1709+
export enum SingletonStringEnum {
1710+
/** 1337 */
1711+
SingletonString = '3L1T3!'
1712+
}
1713+
/**
1714+
* Verifies that singleton enums are handled correctly
1715+
*
1716+
* https://github.com/awslabs/jsii/issues/231
1717+
*/
1718+
export class SingletonInt {
1719+
private constructor() { }
1720+
public isSingletonInt(value: number): boolean {
1721+
return value === SingletonIntEnum.SingletonInt;
1722+
}
1723+
}
1724+
/** A singleton integer. */
1725+
export enum SingletonIntEnum {
1726+
/** Elite! */
1727+
SingletonInt = 1337
1728+
}

packages/jsii-calc/test/assembly.jsii

+127-1
Original file line numberDiff line numberDiff line change
@@ -7121,6 +7121,132 @@
71217121
],
71227122
"name": "SingleInstanceTwoTypes"
71237123
},
7124+
"jsii-calc.SingletonInt": {
7125+
"assembly": "jsii-calc",
7126+
"docs": {
7127+
"remarks": "https://github.com/awslabs/jsii/issues/231",
7128+
"stability": "experimental",
7129+
"summary": "Verifies that singleton enums are handled correctly."
7130+
},
7131+
"fqn": "jsii-calc.SingletonInt",
7132+
"kind": "class",
7133+
"locationInModule": {
7134+
"filename": "lib/compliance.ts",
7135+
"line": 1718
7136+
},
7137+
"methods": [
7138+
{
7139+
"docs": {
7140+
"stability": "experimental"
7141+
},
7142+
"locationInModule": {
7143+
"filename": "lib/compliance.ts",
7144+
"line": 1720
7145+
},
7146+
"name": "isSingletonInt",
7147+
"parameters": [
7148+
{
7149+
"name": "value",
7150+
"type": {
7151+
"primitive": "number"
7152+
}
7153+
}
7154+
],
7155+
"returns": {
7156+
"type": {
7157+
"primitive": "boolean"
7158+
}
7159+
}
7160+
}
7161+
],
7162+
"name": "SingletonInt"
7163+
},
7164+
"jsii-calc.SingletonIntEnum": {
7165+
"assembly": "jsii-calc",
7166+
"docs": {
7167+
"stability": "experimental",
7168+
"summary": "A singleton integer."
7169+
},
7170+
"fqn": "jsii-calc.SingletonIntEnum",
7171+
"kind": "enum",
7172+
"locationInModule": {
7173+
"filename": "lib/compliance.ts",
7174+
"line": 1725
7175+
},
7176+
"members": [
7177+
{
7178+
"docs": {
7179+
"stability": "experimental",
7180+
"summary": "Elite!"
7181+
},
7182+
"name": "SingletonInt"
7183+
}
7184+
],
7185+
"name": "SingletonIntEnum"
7186+
},
7187+
"jsii-calc.SingletonString": {
7188+
"assembly": "jsii-calc",
7189+
"docs": {
7190+
"remarks": "https://github.com/awslabs/jsii/issues/231",
7191+
"stability": "experimental",
7192+
"summary": "Verifies that singleton enums are handled correctly."
7193+
},
7194+
"fqn": "jsii-calc.SingletonString",
7195+
"kind": "class",
7196+
"locationInModule": {
7197+
"filename": "lib/compliance.ts",
7198+
"line": 1701
7199+
},
7200+
"methods": [
7201+
{
7202+
"docs": {
7203+
"stability": "experimental"
7204+
},
7205+
"locationInModule": {
7206+
"filename": "lib/compliance.ts",
7207+
"line": 1704
7208+
},
7209+
"name": "isSingletonString",
7210+
"parameters": [
7211+
{
7212+
"name": "value",
7213+
"type": {
7214+
"primitive": "string"
7215+
}
7216+
}
7217+
],
7218+
"returns": {
7219+
"type": {
7220+
"primitive": "boolean"
7221+
}
7222+
}
7223+
}
7224+
],
7225+
"name": "SingletonString"
7226+
},
7227+
"jsii-calc.SingletonStringEnum": {
7228+
"assembly": "jsii-calc",
7229+
"docs": {
7230+
"stability": "experimental",
7231+
"summary": "A singleton string."
7232+
},
7233+
"fqn": "jsii-calc.SingletonStringEnum",
7234+
"kind": "enum",
7235+
"locationInModule": {
7236+
"filename": "lib/compliance.ts",
7237+
"line": 1709
7238+
},
7239+
"members": [
7240+
{
7241+
"docs": {
7242+
"stability": "experimental",
7243+
"summary": "1337."
7244+
},
7245+
"name": "SingletonString"
7246+
}
7247+
],
7248+
"name": "SingletonStringEnum"
7249+
},
71247250
"jsii-calc.StableClass": {
71257251
"assembly": "jsii-calc",
71267252
"docs": {
@@ -8648,5 +8774,5 @@
86488774
}
86498775
},
86508776
"version": "0.11.2",
8651-
"fingerprint": "eQpFH3EHC2GlCSnThymTxnuO9HyZBFvsvddZqu1Fy+8="
8777+
"fingerprint": "5TwMNffhxUueZQEAGZG6+JIfS6jcTwCJWc9vixH5aLc="
86528778
}

packages/jsii/lib/assembler.ts

+13-6
Original file line numberDiff line numberDiff line change
@@ -735,11 +735,17 @@ export class Assembler implements Emitter {
735735
LOG.trace(`Processing enum: ${colors.gray(ctx.namespace.join('.'))}.${colors.cyan(type.symbol.name)}`);
736736
}
737737

738-
if (_hasInternalJsDocTag(type.symbol)) {
738+
// Forcefully resolving to the EnumDeclaration symbol for single-valued enums
739+
const symbol: ts.Symbol = type.isLiteral() ? (type.symbol as any).parent : type.symbol;
740+
if (!symbol) {
741+
throw new Error(`Unable to resolve enum declaration for ${type.symbol.name}!`);
742+
}
743+
744+
if (_hasInternalJsDocTag(symbol)) {
739745
return undefined;
740746
}
741747

742-
const decl = type.symbol.valueDeclaration;
748+
const decl = symbol.valueDeclaration;
743749
const flags = ts.getCombinedModifierFlags(decl);
744750
// tslint:disable-next-line:no-bitwise
745751
if (flags & ts.ModifierFlags.Const) {
@@ -748,19 +754,20 @@ export class Assembler implements Emitter {
748754
`Exported enum cannot be declared 'const'`);
749755
}
750756

751-
const docs = this._visitDocumentation(type.symbol, ctx);
757+
const docs = this._visitDocumentation(symbol, ctx);
752758

753759
const typeContext = ctx.replaceStability(docs && docs.stability);
760+
const members = type.isUnion() ? type.types : [type];
754761

755762
const jsiiType: spec.EnumType = {
756763
assembly: this.projectInfo.name,
757-
fqn: `${[this.projectInfo.name, ...ctx.namespace].join('.')}.${type.symbol.name}`,
764+
fqn: `${[this.projectInfo.name, ...ctx.namespace].join('.')}.${symbol.name}`,
758765
kind: spec.TypeKind.Enum,
759-
members: ((type as ts.UnionType).types || []).map(m => ({
766+
members: members.map(m => ({
760767
name: m.symbol.name,
761768
docs: this._visitDocumentation(m.symbol, typeContext),
762769
})),
763-
name: type.symbol.name,
770+
name: symbol.name,
764771
namespace: ctx.namespace.length > 0 ? ctx.namespace.join('.') : undefined,
765772
docs
766773
};

0 commit comments

Comments
 (0)