diff --git a/packages/jsii-rosetta/lib/commands/transliterate.ts b/packages/jsii-rosetta/lib/commands/transliterate.ts index 2a50f1007f..d53c69eb93 100644 --- a/packages/jsii-rosetta/lib/commands/transliterate.ts +++ b/packages/jsii-rosetta/lib/commands/transliterate.ts @@ -137,10 +137,17 @@ type Mutable = { -readonly [K in keyof T]: Mutable }; type AssemblyLoader = () => Promise>; 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... diff --git a/packages/jsii-rosetta/lib/fixtures.ts b/packages/jsii-rosetta/lib/fixtures.ts index 09fc28ad07..91276ca1ed 100644 --- a/packages/jsii-rosetta/lib/fixtures.ts +++ b/packages/jsii-rosetta/lib/fixtures.ts @@ -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), @@ -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}`, diff --git a/packages/jsii-rosetta/lib/translate.ts b/packages/jsii-rosetta/lib/translate.ts index 9d917f6823..65d5cc643c 100644 --- a/packages/jsii-rosetta/lib/translate.ts +++ b/packages/jsii-rosetta/lib/translate.ts @@ -149,10 +149,16 @@ 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... @@ -160,6 +166,25 @@ export class SnippetTranslator { } 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( + 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) { diff --git a/packages/jsii-rosetta/test/commands/transliterate.test.ts b/packages/jsii-rosetta/test/commands/transliterate.test.ts index 6f11824fbb..c436098e06 100644 --- a/packages/jsii-rosetta/test/commands/transliterate.test.ts +++ b/packages/jsii-rosetta/test/commands/transliterate.test.ts @@ -145,7 +145,9 @@ export class ClassName implements IInterface { "markdown": "# README \`\`\`csharp - // Example automatically generated. See https://github.com/aws/jsii/issues/826 + // This example was automatically transliterated. + // See https://github.com/aws/jsii/issues/826 for more information. + IInterface object = new ClassName(\\"this\\", 1337, new ClassNameProps { Foo = \\"bar\\" }); object.Property = EnumType.OPTION_A; object.MethodCall(); @@ -169,7 +171,9 @@ export class ClassName implements IInterface { "fqn": "testpkg.ClassName", "initializer": Object { "docs": Object { - "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + "example": "// This example was automatically transliterated. + // See https://github.com/aws/jsii/issues/826 for more information. + new ClassName(\\"this\\", 1337, new ClassNameProps { Property = EnumType.OPTION_B });", "summary": "Create a new instance of ClassName.", }, @@ -209,7 +213,9 @@ export class ClassName implements IInterface { "methods": Array [ Object { "docs": Object { - "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + "example": "// This example was automatically transliterated. + // See https://github.com/aws/jsii/issues/826 for more information. + ClassName.StaticMethod();", "remarks": "It can be invoked easily.", "summary": "A static method.", @@ -302,7 +308,9 @@ export class ClassName implements IInterface { "testpkg.EnumType": Object { "assembly": "testpkg", "docs": Object { - "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + "example": "// This example was automatically transliterated. + // See https://github.com/aws/jsii/issues/826 for more information. + new ClassName(\\"this\\", 1337, new ClassNameProps { Property = EnumType.OPTION_B });", }, "fqn": "testpkg.EnumType", @@ -314,14 +322,18 @@ export class ClassName implements IInterface { "members": Array [ Object { "docs": Object { - "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + "example": "// This example was automatically transliterated. + // See https://github.com/aws/jsii/issues/826 for more information. + new ClassName(\\"this\\", 1337, new ClassNameProps { Property = EnumType.OPTION_A });", }, "name": "OPTION_A", }, Object { "docs": Object { - "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + "example": "// This example was automatically transliterated. + // See https://github.com/aws/jsii/issues/826 for more information. + new ClassName(\\"this\\", 1337, new ClassNameProps { Property = EnumType.OPTION_B });", }, "name": "OPTION_B", @@ -341,7 +353,9 @@ export class ClassName implements IInterface { Object { "abstract": true, "docs": Object { - "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + "example": "// This example was automatically transliterated. + // See https://github.com/aws/jsii/issues/826 for more information. + iface.MethodCall();", "summary": "An instance method call.", }, @@ -357,7 +371,9 @@ export class ClassName implements IInterface { Object { "abstract": true, "docs": Object { - "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + "example": "// This example was automatically transliterated. + // See https://github.com/aws/jsii/issues/826 for more information. + iface.Property = EnumType.OPTION_B;", "summary": "A property value.", }, @@ -409,7 +425,9 @@ export class ClassName implements IInterface { "markdown": "# README \`\`\`java - // Example automatically generated. See https://github.com/aws/jsii/issues/826 + // This example was automatically transliterated. + // See https://github.com/aws/jsii/issues/826 for more information. + IInterface object = new ClassName(\\"this\\", 1337, new ClassNameProps().foo(\\"bar\\")); object.getProperty() = EnumType.getOPTION_A(); object.methodCall(); @@ -433,7 +451,9 @@ export class ClassName implements IInterface { "fqn": "testpkg.ClassName", "initializer": Object { "docs": Object { - "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + "example": "// This example was automatically transliterated. + // See https://github.com/aws/jsii/issues/826 for more information. + new ClassName(\\"this\\", 1337, new ClassNameProps().property(EnumType.getOPTION_B()));", "summary": "Create a new instance of ClassName.", }, @@ -473,7 +493,9 @@ export class ClassName implements IInterface { "methods": Array [ Object { "docs": Object { - "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + "example": "// This example was automatically transliterated. + // See https://github.com/aws/jsii/issues/826 for more information. + ClassName.staticMethod();", "remarks": "It can be invoked easily.", "summary": "A static method.", @@ -566,7 +588,9 @@ export class ClassName implements IInterface { "testpkg.EnumType": Object { "assembly": "testpkg", "docs": Object { - "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + "example": "// This example was automatically transliterated. + // See https://github.com/aws/jsii/issues/826 for more information. + new ClassName(\\"this\\", 1337, new ClassNameProps().property(EnumType.getOPTION_B()));", }, "fqn": "testpkg.EnumType", @@ -578,14 +602,18 @@ export class ClassName implements IInterface { "members": Array [ Object { "docs": Object { - "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + "example": "// This example was automatically transliterated. + // See https://github.com/aws/jsii/issues/826 for more information. + new ClassName(\\"this\\", 1337, new ClassNameProps().property(EnumType.getOPTION_A()));", }, "name": "OPTION_A", }, Object { "docs": Object { - "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + "example": "// This example was automatically transliterated. + // See https://github.com/aws/jsii/issues/826 for more information. + new ClassName(\\"this\\", 1337, new ClassNameProps().property(EnumType.getOPTION_B()));", }, "name": "OPTION_B", @@ -605,7 +633,9 @@ export class ClassName implements IInterface { Object { "abstract": true, "docs": Object { - "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + "example": "// This example was automatically transliterated. + // See https://github.com/aws/jsii/issues/826 for more information. + iface.methodCall();", "summary": "An instance method call.", }, @@ -621,7 +651,9 @@ export class ClassName implements IInterface { Object { "abstract": true, "docs": Object { - "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + "example": "// This example was automatically transliterated. + // See https://github.com/aws/jsii/issues/826 for more information. + iface.getProperty() = EnumType.getOPTION_B();", "summary": "A property value.", }, @@ -673,7 +705,9 @@ export class ClassName implements IInterface { "markdown": "# README \`\`\`python - # Example automatically generated. See https://github.com/aws/jsii/issues/826 + # This example was automatically transliterated. + # See https://github.com/aws/jsii/issues/826 for more information. + object = ClassName(\\"this\\", 1337, foo=\\"bar\\") object.property = EnumType.OPTION_A object.method_call() @@ -697,7 +731,9 @@ export class ClassName implements IInterface { "fqn": "testpkg.ClassName", "initializer": Object { "docs": Object { - "example": "# Example automatically generated. See https://github.com/aws/jsii/issues/826 + "example": "# This example was automatically transliterated. + # See https://github.com/aws/jsii/issues/826 for more information. + ClassName(\\"this\\", 1337, property=EnumType.OPTION_B)", "summary": "Create a new instance of ClassName.", }, @@ -737,7 +773,9 @@ export class ClassName implements IInterface { "methods": Array [ Object { "docs": Object { - "example": "# Example automatically generated. See https://github.com/aws/jsii/issues/826 + "example": "# This example was automatically transliterated. + # See https://github.com/aws/jsii/issues/826 for more information. + ClassName.static_method()", "remarks": "It can be invoked easily.", "summary": "A static method.", @@ -830,7 +868,9 @@ export class ClassName implements IInterface { "testpkg.EnumType": Object { "assembly": "testpkg", "docs": Object { - "example": "# Example automatically generated. See https://github.com/aws/jsii/issues/826 + "example": "# This example was automatically transliterated. + # See https://github.com/aws/jsii/issues/826 for more information. + ClassName(\\"this\\", 1337, property=EnumType.OPTION_B)", }, "fqn": "testpkg.EnumType", @@ -842,14 +882,18 @@ export class ClassName implements IInterface { "members": Array [ Object { "docs": Object { - "example": "# Example automatically generated. See https://github.com/aws/jsii/issues/826 + "example": "# This example was automatically transliterated. + # See https://github.com/aws/jsii/issues/826 for more information. + ClassName(\\"this\\", 1337, property=EnumType.OPTION_A)", }, "name": "OPTION_A", }, Object { "docs": Object { - "example": "# Example automatically generated. See https://github.com/aws/jsii/issues/826 + "example": "# This example was automatically transliterated. + # See https://github.com/aws/jsii/issues/826 for more information. + ClassName(\\"this\\", 1337, property=EnumType.OPTION_B)", }, "name": "OPTION_B", @@ -869,7 +913,9 @@ export class ClassName implements IInterface { Object { "abstract": true, "docs": Object { - "example": "# Example automatically generated. See https://github.com/aws/jsii/issues/826 + "example": "# This example was automatically transliterated. + # See https://github.com/aws/jsii/issues/826 for more information. + iface.method_call()", "summary": "An instance method call.", }, @@ -885,7 +931,9 @@ export class ClassName implements IInterface { Object { "abstract": true, "docs": Object { - "example": "# Example automatically generated. See https://github.com/aws/jsii/issues/826 + "example": "# This example was automatically transliterated. + # See https://github.com/aws/jsii/issues/826 for more information. + iface.property = EnumType.OPTION_B", "summary": "A property value.", }, @@ -914,7 +962,11 @@ test('single assembly, loose mode', () => 'README.md': ` # Missing literate source -[example is not found](missing-example.lit.ts) +[ts source is not found](missing-example.lit.ts) + +# Missing literate source and fallback + +[example is not found](omit-example.lit.ts) # Missing fixture @@ -940,6 +992,14 @@ import { SampleClass } from './index'; /// ## This is a heading within the literate file! new SampleClass('literate'); `, + // The `lit.ts` source file and `lit.js` output will not be there in packaged form... + 'omit-example.lit.ts': ` +import { SampleClass } from './index'; + +/// !show +/// ## This is a heading within the omitted literate file! +new SampleClass('omitted-literate'); + `, }); fs.writeJsonSync( path.join(tmpDir, SPEC_FILE_NAME), @@ -949,6 +1009,9 @@ new SampleClass('literate'); }, ); for (const [file, content] of Object.entries(compilationResult.files)) { + if (file.startsWith('omit-')) { + continue; + } fs.writeFileSync(path.resolve(tmpDir, file), content, 'utf-8'); } @@ -994,14 +1057,29 @@ new SampleClass('literate'); ## This is a heading within the literate file! \`\`\`csharp - // Example automatically generated without compilation. See https://github.com/aws/jsii/issues/826 - new index_1.SampleClass(\\"literate\\"); + // This example was automatically transliterated with incomplete type information. It may not work as-is. + // See https://github.com/aws/jsii/issues/826 for more information. + + new SampleClass(\\"literate\\"); + \`\`\` + + # Missing literate source and fallback + + ## This is a heading within the omitted literate file! + + \`\`\`csharp + // This example was automatically transliterated with incomplete type information. It may not work as-is. + // See https://github.com/aws/jsii/issues/826 for more information. + + new SampleClass(\\"omitted-literate\\"); \`\`\` # Missing fixture \`\`\`csharp - // Example automatically generated without compilation. See https://github.com/aws/jsii/issues/826 + // This example was automatically transliterated with incomplete type information. It may not work as-is. + // See https://github.com/aws/jsii/issues/826 for more information. + new SampleClass(\\"README.md\\"); \`\`\`", }, @@ -1019,7 +1097,9 @@ new SampleClass('literate'); "testpkg.SampleClass": Object { "assembly": "testpkg", "docs": Object { - "example": "// Example automatically generated without compilation. See https://github.com/aws/jsii/issues/826 + "example": "// This example was automatically transliterated with incomplete type information. It may not work as-is. + // See https://github.com/aws/jsii/issues/826 for more information. + new DoesNotCompile(this, \\"That\\", new Struct { Foo = 1337 });", }, "fqn": "testpkg.SampleClass", @@ -1084,14 +1164,29 @@ new SampleClass('literate'); ## This is a heading within the literate file! \`\`\`java - // Example automatically generated without compilation. See https://github.com/aws/jsii/issues/826 + // This example was automatically transliterated with incomplete type information. It may not work as-is. + // See https://github.com/aws/jsii/issues/826 for more information. + new SampleClass(\\"literate\\"); \`\`\` + # Missing literate source and fallback + + ## This is a heading within the omitted literate file! + + \`\`\`java + // This example was automatically transliterated with incomplete type information. It may not work as-is. + // See https://github.com/aws/jsii/issues/826 for more information. + + new SampleClass(\\"omitted-literate\\"); + \`\`\` + # Missing fixture \`\`\`java - // Example automatically generated without compilation. See https://github.com/aws/jsii/issues/826 + // This example was automatically transliterated with incomplete type information. It may not work as-is. + // See https://github.com/aws/jsii/issues/826 for more information. + new SampleClass(\\"README.md\\"); \`\`\`", }, @@ -1109,7 +1204,9 @@ new SampleClass('literate'); "testpkg.SampleClass": Object { "assembly": "testpkg", "docs": Object { - "example": "// Example automatically generated without compilation. See https://github.com/aws/jsii/issues/826 + "example": "// This example was automatically transliterated with incomplete type information. It may not work as-is. + // See https://github.com/aws/jsii/issues/826 for more information. + DoesNotCompile.Builder.create(this, \\"That\\").foo(1337).build();", }, "fqn": "testpkg.SampleClass", @@ -1174,14 +1271,29 @@ new SampleClass('literate'); ## This is a heading within the literate file! \`\`\`python - # Example automatically generated without compilation. See https://github.com/aws/jsii/issues/826 - index_1.SampleClass(\\"literate\\") + # This example was automatically transliterated with incomplete type information. It may not work as-is. + # See https://github.com/aws/jsii/issues/826 for more information. + + SampleClass(\\"literate\\") + \`\`\` + + # Missing literate source and fallback + + ## This is a heading within the omitted literate file! + + \`\`\`python + # This example was automatically transliterated with incomplete type information. It may not work as-is. + # See https://github.com/aws/jsii/issues/826 for more information. + + SampleClass(\\"omitted-literate\\") \`\`\` # Missing fixture \`\`\`python - # Example automatically generated without compilation. See https://github.com/aws/jsii/issues/826 + # This example was automatically transliterated with incomplete type information. It may not work as-is. + # See https://github.com/aws/jsii/issues/826 for more information. + SampleClass(\\"README.md\\") \`\`\`", }, @@ -1199,7 +1311,9 @@ new SampleClass('literate'); "testpkg.SampleClass": Object { "assembly": "testpkg", "docs": Object { - "example": "# Example automatically generated without compilation. See https://github.com/aws/jsii/issues/826 + "example": "# This example was automatically transliterated with incomplete type information. It may not work as-is. + # See https://github.com/aws/jsii/issues/826 for more information. + DoesNotCompile(self, \\"That\\", foo=1337)", }, "fqn": "testpkg.SampleClass", diff --git a/packages/jsii-rosetta/test/translate.test.ts b/packages/jsii-rosetta/test/translate.test.ts new file mode 100644 index 0000000000..3ddbe50456 --- /dev/null +++ b/packages/jsii-rosetta/test/translate.test.ts @@ -0,0 +1,40 @@ +import { SnippetTranslator, TypeScriptSnippet } from '../lib'; +import { VisualizeAstVisitor } from '../lib/languages/visualize'; + +test('does not fail on "Debug Failure"', () => { + // GIVEN + const snippet: TypeScriptSnippet = { + completeSource: + 'Missing literate source file test/integ.restapi-import.lit.ts', + where: '@aws-cdk.aws-apigateway-README-snippet4', + visibleSource: + "import { App, CfnOutput, NestedStack, NestedStackProps, Stack } from '@aws-cdk/core';\nimport { Construct } from 'constructs';\nimport { Deployment, Method, MockIntegration, PassthroughBehavior, RestApi, Stage } from '../lib';\n\n/**\n * This file showcases how to split up a RestApi's Resources and Methods across nested stacks.\n *\n * The root stack 'RootStack' first defines a RestApi.\n * Two nested stacks BooksStack and PetsStack, create corresponding Resources '/books' and '/pets'.\n * They are then…;\n\n readonly methods?: Method[];\n}\n\nclass DeployStack extends NestedStack {\n constructor(scope: Construct, props: DeployStackProps) {\n super(scope, 'integ-restapi-import-DeployStack', props);\n\n const deployment = new Deployment(this, 'Deployment', {\n api: RestApi.fromRestApiId(this, 'RestApi', props.restApiId),\n });\n (props.methods ?? []).forEach((method) => deployment.node.addDependency(method));\n new Stage(this, 'Stage', { deployment });\n }\n}\n\nnew RootStack(new App());", + parameters: { lit: 'test/integ.restapi-import.lit.ts' }, + strict: false, + }; + + // WHEN + const subject = new SnippetTranslator(snippet, { + includeCompilerDiagnostics: true, + }); + + // THEN + expect(subject.renderUsing(new VisualizeAstVisitor())).toMatchInlineSnapshot(` + "(ExpressionStatement Missing + (Identifier Missing))(ExpressionStatement literate + (Identifier literate))(ExpressionStatement source + (Identifier source))(ExpressionStatement file + (Identifier file))(ExpressionStatement test/integ.restapi-import.lit.ts + (BinaryExpression test/integ.restapi-import.lit.ts + (BinaryExpression test/integ.restapi + (Identifier test) + (SlashToken /) + (PropertyAccessExpression integ.restapi + (Identifier integ) + (Identifier restapi))) + (MinusToken -) + (PropertyAccessExpression import.lit.ts + import.lit + (Identifier ts))))" + `); +});