TypeScript Restrictions
Since the API exported by jsii modules will be represented in a variety of other languages, certain restrictions are
enforced by the jsii
compiler.
Info
Since those restrictions are solely intended to ensure the exported API can be represented in other languages, they
do not apply to any internal declarations such as private
members and declarations annotated with the
/** @internal */
tag.
Dependencies
A jsii module can declare dependencies on any other jsii module by adding entries in the dependencies
key in the
package.json
file. Since most other platforms do not support multiple different versions of the same library to
coexist in the same closure, it is recommended to also declare all such dependencies as peerDependencies
.
Occasionally, a dependency on a non-jsii module is useful. Since such dependencies do not have generated bindings in
all the supported languages, they must be bundled with the jsii module that depends on them, by adding the library
into the bundleDependencies
array in package.json
. The API of the jsii module can not expose any type from bundled
dependencies, since those types would not be available in other languages.
Info
For more information on package.json
file contents, refer to the npm documentation.
Naming
Class Members
Members of classes cannot share the same PascalCased representation as the declaring class itself, as this results in invalid C# code:
export class Foo {
// 💥 Illegal property name
public foo: string;
}
export class Bar {
// 💥 Illegal method name
public bar(): void { /* ... */ }
}
Danger
Due to existing usage, this restriction is only enforced when jsii
is invoked with the --strict
option. The
generated C# class name will receive a _
suffix if any of it's members conflict, which is a breaking change to
existing .NET consumers when a conflicting member is introduced after the initial release.
Interfaces
The jsii
type model distinguishes two kinds of interfaces:
- Behavioral interfaces, which can declare methods and properties
- Structs, which are immutable pure data entities, and can consequently only declare
readonly
properties
A name convention is used to distinguish between these two: behavioral interfaces must have a name that starts with a
I
prefix, while structs must not have such a prefix.
Info
The /** @struct */
type system hint can be used to force an interface with a name starting with the I
prefix to be handled as a struct instead of a behavioral interface.
/**
* Since there is no `I` prefix, Foo is considered to be a struct.
*/
export interface Foo {
// 💥 Structs are not allowed to declare methods
foo(): void;
// 💥 Structs are not allowed to declare mutable properties
mutableProperty: number;
// ✅ Structs can declare immutable properties
readonly immutableProperty: string;
}
/**
* Since there is an `I` prefix, IFoo is considered to be a behavioral interface.
*/
export interface IFoo {
// ✅ Behavioral interfaces can declare methods
foo(): void;
// ✅ Behavioral interfaces can declare mutable properties
mutableProperty: number;
// ✅ Behavioral interfaces can declare immutable properties
readonly immutableProperty: string;
}
Inheritance
Structs
As structs are pure data entities, they can only be extended by other structs:
export interface Struct { /* ...readonly properties... */ }
// 💥 Structs cannot be extended by behavioral interfaces
export interface IBehavioralInterface extends Struct { /* ... */ }
// 💥 Structs cannot be implemented by classes
export class ConcreteType implements Struct { /* ... */ }
// ✅ Structs can extend other structs
export interface SuperStruct extends Struct { /* ...readonly properties */ }
Member Visibility
The C# language does not allow overriding members to change the visibility of the overridden declaration, even if the updated visibility is more permissive. As a consequence, overridden members are required to retain the same visibility as their parents.
Danger
This makes changing the visibility of a protected
member to public
is a breaking change in jsii
libraries!
export class Base {
protected method(): void { /* ... */ }
}
export class Subclass extends Base {
// 💥 Illegal change of visibility from protected to public
public method(): void { /* ... */ }
}
Covariant Overrides & Parameter List Changes
In TypeScript, overriding members can have a signature that differs from the overridden member as long as the new signature is compatible with the parent. This is however not supported as:
- Java and C# do not support omitting parameters when overriding or implementing a member
- C# does not support overriding or implementing a member using covariant parameter or return types
Note
C# 9 introduces support for covariant return types, which would allow relaxing this restriction, however jsii
must build code targetting the .NET Core 3.1
runtime, which only supports C# 8. Once .NET Core 3.1
becimes
end-of-life, this may change.
export class Base {
public method(param: any): any { /* ... */ }
}
export class Child extends Base {
// 💥 Parameter signatures do not match
public method(): any { /* ... */ }
// 💥 Parameter types do not match, even though they are covariant
public method(param: string): any { /* ... */ }
// 💥 Return type does not match, even though it is covariant
public method(param: any): string { /* ... */ }
}
Parameterized Types (aka: Generics)
Parameterized types are not consistently supported in all supported target languages. Go does not currently support
generics, and the differences in generic semantics between TypeScript, C# and Java make it difficult to
correctly represent such types in all those languages. As a consequence, jsii
does not support declaring parameterized
types.
Only certain built-in parameterized types can be used in jsii
modules:
Array<T>
, which is equivalent toT[]
Record<string, T>
, which is equivalent to{ [key: string]: T }
Promise<T>
, which is the return type of any asynchronous method
// 💥 Parameterized types cannot be introduced
export interface Parameterized<T> {
readonly value: T;
}
export interface IAsyncFooMaker {
// ✅ Asynchronous methods must return promises
makeFoo(): Promise<Foo>;
// ✅ Arrays are supported
makeFoos(count: number): Array<Promise<Foo>>;
}
Soft-Reserved Words
In order to guarantee a consistent developer experience across all supported languages, jsii
emits warnings whenever
a declaration is named after any target language's reserved words, as those will need renaming in target languages:
C# | Java | Python | Go |
---|---|---|---|
abstract |
abstract |
False |
break |
base |
assert |
None |
case |
bool |
boolean |
True |
chan |
byte |
byte |
and |
const |
char |
char |
assert |
continue |
checked |
double |
def |
default |
decimal |
final |
del |
defer |
delegate |
float |
elif |
else |
double |
goto |
except |
fallthrough |
event |
int |
from |
for |
explicit |
long |
global |
func |
extern |
native |
is |
go |
fixed |
short |
lambda |
goto |
float |
strictfp |
nonlocal |
if |
foreach |
synchronized |
not |
import |
goto |
throws |
or |
interface |
implicit |
transient |
pass |
map |
int |
volatile |
raise |
package |
internal |
range |
||
is |
return |
||
lock |
select |
||
long |
struct |
||
namespace |
switch |
||
object |
type |
||
operator |
var |
||
out |
|||
override |
|||
params |
|||
readonly |
|||
ref |
|||
sbyte |
|||
sealed |
|||
short |
|||
sizeof |
|||
stackalloc |
|||
string |
|||
struct |
|||
uint |
|||
ulong |
|||
unchecked |
|||
unsafe |
|||
ushort |
|||
using |
|||
virtual |
|||
volatile |
Info
The list of reserved words is derived from jsii/lib/reserved-words.ts
.