Skip to content

Commit 7ba1aab

Browse files
authored
fix(assembler): handle unknown types without crashing (#501)
After failing compilation we proceed with jsii analysis, to give as much information as possible in one go. However, some compilation failures lead to a lack of information that make the jsii analyzer crash, which ultimately leads jsii to fail with no more information than: Error: TypeError: Cannot read property 'getJsDocTags' of undefined Fix this in two ways: - First, detect the situation where this occurs and produce a more useful error message. - Guard against this happening in general by catching exceptions during type analysis and still printing the compilation errors.
1 parent 04c061e commit 7ba1aab

2 files changed

Lines changed: 44 additions & 12 deletions

File tree

packages/jsii/lib/assembler.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,6 @@ export class Assembler implements Emitter {
377377

378378
const processBaseTypes = (types: ts.Type[]) => {
379379
for (const iface of types) {
380-
381380
// base is private/internal, so we continue recursively with it's own bases
382381
if (this._isPrivateOrInternal(iface.symbol)) {
383382
erasedBases.push(iface);
@@ -507,7 +506,7 @@ export class Assembler implements Emitter {
507506
// process all "implements" clauses
508507
const allInterfaces = new Set<string>();
509508
for (const clause of implementsClauses) {
510-
const { interfaces } = await this._processBaseInterfaces(fqn, clause.types.map(t => this._typeChecker.getTypeFromTypeNode(t)));
509+
const { interfaces } = await this._processBaseInterfaces(fqn, clause.types.map(t => this._getTypeFromTypeNode(t)));
511510
for (const ifc of (interfaces || [])) {
512511
allInterfaces.add(ifc.fqn);
513512
}
@@ -623,6 +622,17 @@ export class Assembler implements Emitter {
623622
return _sortMembers(jsiiType);
624623
}
625624

625+
/**
626+
* Use the TypeChecker's getTypeFromTypeNode, but throw a descriptive error if it fails
627+
*/
628+
private _getTypeFromTypeNode(t: ts.TypeNode) {
629+
const type = this._typeChecker.getTypeFromTypeNode(t);
630+
if (isErrorType(type)) {
631+
throw new Error(`Unable to resolve type: ${t.getFullText()}. This typically happens if something is wrong with your dependency closure.`);
632+
}
633+
return type;
634+
}
635+
626636
/**
627637
* Check that this class doesn't declare any members that are of different staticness in itself or any of its bases
628638
*/
@@ -1585,3 +1595,13 @@ function noEmptyDict<T>(xs: {[key: string]: T}): {[key: string]: T} | undefined
15851595
if (Object.keys(xs).length === 0) { return undefined; }
15861596
return xs;
15871597
}
1598+
1599+
/**
1600+
* Check whether this type is the intrinsic TypeScript "error type"
1601+
*
1602+
* This type is returned if type lookup fails. Unfortunately no public
1603+
* accessors for it are exposed.
1604+
*/
1605+
function isErrorType(t: ts.Type) {
1606+
return (t as any).intrinsicName === 'error';
1607+
}

packages/jsii/lib/compiler.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -141,22 +141,30 @@ export class Compiler implements Emitter {
141141

142142
private async _consumeProgram(program: ts.Program, stdlib: string): Promise<EmitResult> {
143143
const emit = program.emit();
144-
if (emit.emitSkipped) {
144+
let hasErrors = emitHasErrors(emit);
145+
const diagnostics = [...emit.diagnostics];
146+
147+
if (hasErrors) {
145148
LOG.error('Compilation errors prevented the JSII assembly from being created');
146149
}
147150

148151
// we continue to do jsii checker even if there are compilation errors so that
149-
// jsii warnings will appear.
150-
const assembler = new Assembler(this.options.projectInfo, program, stdlib);
151-
const assmEmit = await assembler.emit();
152-
if (assmEmit.hasErrors) {
153-
LOG.error('Type model errors prevented the JSII assembly from being created');
152+
// jsii warnings will appear. However, the Assembler might throw an exception
153+
// because broken/missing type information might lead it to fail completely.
154+
try {
155+
const assembler = new Assembler(this.options.projectInfo, program, stdlib);
156+
const assmEmit = await assembler.emit();
157+
if (assmEmit.hasErrors) {
158+
LOG.error('Type model errors prevented the JSII assembly from being created');
159+
}
160+
161+
hasErrors = hasErrors || assmEmit.hasErrors;
162+
diagnostics.push(...assmEmit.diagnostics);
163+
} catch (e) {
164+
LOG.error(`Error during type model analysis: ${e}`);
154165
}
155166

156-
return {
157-
hasErrors: emit.emitSkipped || assmEmit.hasErrors,
158-
diagnostics: [...emit.diagnostics, ...assmEmit.diagnostics]
159-
};
167+
return { hasErrors, diagnostics };
160168
}
161169

162170
/**
@@ -353,3 +361,7 @@ function parseConfigHostFromCompilerHost(host: ts.CompilerHost): ts.ParseConfigH
353361
trace: host.trace ? (s) => host.trace!(s) : undefined
354362
};
355363
}
364+
365+
function emitHasErrors(result: ts.EmitResult) {
366+
return result.diagnostics.some(d => d.category === ts.DiagnosticCategory.Error) || result.emitSkipped;
367+
}

0 commit comments

Comments
 (0)