Skip to content

Commit

Permalink
fix(rosetta): fails on "Debug Failure" (#2917)
Browse files Browse the repository at this point in the history
In cases where a literate source file was missing, the substitution
value was not valid TypeScript, which could cause the comipler to fail
on an opaque error (`Debug Failure`).

This falls back to the visible code for the snippet instead of inserting a
placeholder, and also makes sure to rescue the `Debug Failure` where
it is emitted from, that is in the call to
`program.getDeclarationDiagnostics`.

Both of this changes result in greater chances of transliteration
success.

Related to cdklabs/jsii-docgen#369



---

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
  • Loading branch information
RomainMuller authored Jul 21, 2021
1 parent 9d554ed commit f6078ef
Show file tree
Hide file tree
Showing 5 changed files with 239 additions and 58 deletions.
15 changes: 11 additions & 4 deletions packages/jsii-rosetta/lib/commands/transliterate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,17 @@ type Mutable<T> = { -readonly [K in keyof T]: Mutable<T[K]> };
type AssemblyLoader = () => Promise<Mutable<Assembly>>;

function prefixDisclaimer(translation: Translation): string {
const message = translation.didCompile
? 'Example automatically generated. See https://github.com/aws/jsii/issues/826'
: 'Example automatically generated without compilation. See https://github.com/aws/jsii/issues/826';
return `${commentToken()} ${message}\n${translation.source}`;
const comment = commentToken();
const disclaimer = translation.didCompile
? 'This example was automatically transliterated.'
: 'This example was automatically transliterated with incomplete type information. It may not work as-is.';

return [
`${comment} ${disclaimer}`,
`${comment} See https://github.com/aws/jsii/issues/826 for more information.`,
'',
translation.source,
].join('\n');

function commentToken() {
// This is future-proofed a bit, but don't read too much in this...
Expand Down
23 changes: 9 additions & 14 deletions packages/jsii-rosetta/lib/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,14 @@ export function fixturize(
if (literateSource) {
// Compatibility with the "old school" example inclusion mechanism.
// Completely load this file and attach a parameter with its directory.
source = loadLiterateSource(directory, literateSource, loose);
try {
source = loadLiterateSource(directory, literateSource);
} catch (ex) {
// In loose mode, we ignore this failure and stick to the visible source.
if (!loose) {
throw ex;
}
}
parameters[SnippetParameters.$COMPILATION_DIRECTORY] = path.join(
directory,
path.dirname(literateSource),
Expand All @@ -48,22 +55,10 @@ export function fixturize(
};
}

function loadLiterateSource(
directory: string,
literateFileName: string,
loose = false,
) {
function loadLiterateSource(directory: string, literateFileName: string) {
const fullPath = path.join(directory, literateFileName);
const exists = fs.existsSync(fullPath);
if (!exists) {
if (loose) {
// In loose mode, we'll fall back to the `.js` file if it exists...
const jsFile = fullPath.replace(/\.ts(x?)$/, '.js$1');
if (fs.existsSync(jsFile)) {
return fs.readFileSync(jsFile, { encoding: 'utf-8' });
}
return `Missing literate source file ${literateFileName}`;
}
// This couldn't really happen in practice, but do the check anyway
throw new Error(
`Sample uses literate source ${literateFileName}, but not found: ${fullPath}`,
Expand Down
33 changes: 29 additions & 4 deletions packages/jsii-rosetta/lib/translate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,17 +149,42 @@ export class SnippetTranslator {
if (options.includeCompilerDiagnostics || snippet.strict) {
const program = this.compilation.program;
const diagnostics = [
...program.getGlobalDiagnostics(),
...program.getSyntacticDiagnostics(this.compilation.rootFile),
...program.getDeclarationDiagnostics(this.compilation.rootFile),
...program.getSemanticDiagnostics(this.compilation.rootFile),
...neverThrowing(program.getGlobalDiagnostics)(),
...neverThrowing(program.getSyntacticDiagnostics)(
this.compilation.rootFile,
),
...neverThrowing(program.getDeclarationDiagnostics)(
this.compilation.rootFile,
),
...neverThrowing(program.getSemanticDiagnostics)(
this.compilation.rootFile,
),
];
if (snippet.strict) {
// In a strict assembly, so we'll need to brand all diagnostics here...
diagnostics.forEach(annotateStrictDiagnostic);
}
this.compileDiagnostics.push(...diagnostics);
}

/**
* Intercepts all exceptions thrown by the wrapped call, and logs them to
* console.error instead of re-throwing, then returns an empty array. This
* is here to avoid compiler crashes due to broken code examples that cause
* the TypeScript compiler to hit a "Debug Failure".
*/
function neverThrowing<A extends unknown[], R>(
call: (...args: A) => readonly R[],
): (...args: A) => readonly R[] {
return (...args: A) => {
try {
return call(...args);
} catch (err) {
console.error(`Failed to execute ${call.name}: ${err}`);
return [];
}
};
}
}

public renderUsing(visitor: AstHandler<any>) {
Expand Down
Loading

0 comments on commit f6078ef

Please sign in to comment.